CodeBuddy 学习(2):对话模式深度使用

CodeBuddy 学习(2):对话模式深度使用

一、概述:三种模式的内在逻辑

1.1 为什么需要三种模式?

对话式 AI 编程的核心矛盾在于控制权分配------什么时候 AI 可以自主行动,什么时候必须等待你的确认。

场景 控制权归属 匹配模式
提问/学习/讨论 你完全掌控,AI 只输出信息 Ask
明确编码任务 AI 直接执行,你逐条审查 Craft
复杂不确定任务 AI 先规划,你确认后执行 Plan

三种模式不是并列选项,而是一条控制权逐级释放的递进路径

复制代码
Ask(只问不做)→ Craft(边做边调)→ Plan(先想后做)

核心原则:你越清楚要做什么,越可以交给 AI 直接执行;你越不确定,越应该先用 Ask/Plan 收敛思路。


二、Ask 模式:只读问答

2.1 原理讲解

Ask 模式的权限约束是最严格的------不允许修改任何文件、不允许执行任何命令

工作原理

  1. AI 接收你的问题,同时从当前打开的文件和项目中收集上下文。
  2. 通过 @ 引用可以在当前消息中注入特定文件的完整内容。
  3. AI 基于上下文生成回复,但所有回复都是纯文本,不会产生任何副作用。
  4. 回复内容中可能包含代码片段,但需要你手动复制到文件。

适用场景

场景 你问什么 得到什么
学习 API "useEffect cleanup 怎么写?" 代码片段 + 使用说明
分析代码 "解释这段业务逻辑" 分步注释 + 风险标注
方案决策 "方案 A 和 B 哪个好?" 结构化对比分析

2.2 示例:分析遗留代码

完整可运行的场景

假设项目中有以下代码文件 order.ts

typescript 复制代码
// order.ts --- 订单处理模块(遗留代码)

interface OrderItem {
  id: number;
  price: number;
  quantity: number;
}

interface Order {
  id: number;
  items: OrderItem[];
  status: 'pending' | 'paid' | 'cancelled' | 'returned';
  userBalance: number;
}

/**
 * 处理订单状态流转和扣款
 * 注意:此函数在遗留系统中被 12 个模块调用,重构需谨慎
 */
function processOrder(order: Order): Order {
  // 取消和退货直接返回
  if (order.status === 'cancelled' || order.status === 'returned') {
    return { ...order };
  }

  // 已支付不再处理
  if (order.status === 'paid') {
    return { ...order };
  }

  // 计算总价
  let total = 0;
  for (let i = 0; i < order.items.length; i++) {
    total += order.items[i].price * order.items[i].quantity;
  }

  // 空订单免费?
  if (total === 0) {
    return { ...order, status: 'paid' };
  }

  // 余额不足
  if (order.userBalance < total) {
    throw new Error('余额不足');
  }

  // 扣款并标记已支付
  return {
    ...order,
    status: 'paid',
    userBalance: order.userBalance - total,
  };
}

export { processOrder };
export type { Order, OrderItem };
操作步骤
  1. 在 CodeBuddy 中打开 order.ts

  2. 切换到 Ask 模式。

  3. 在对话框中输入:

    markdown 复制代码
    @file order.ts
    请分析 processOrder 函数,包括:
    1. 状态流转分支
    2. 潜在的 Bug 和风险
    3. 数据库事务重写的注意事项
预期输出

AI 会输出类似以下的结构化分析:

sql 复制代码
状态流转分析:

cancelled / returned → 直接返回(不处理)
pending → 计算总价 → 检查余额 → 扣款 → 标记 paid
paid → 直接返回(幂等保护)

风险标注:

🔴 高:高并发超扣风险
如果两个请求同时读取到相同的 userBalance,各自扣款后都认为余额足够,
最终超扣。数据库层应使用 SELECT ... FOR UPDATE 或乐观锁(version 字段)。

🟡 中:数据一致性问题
函数只在内存中修改 order 对象,如果没有持久化步骤,状态变更会丢失。
建议增加一个 orderRepository.save(order) 调用点。

🟢 低:空数组免费漏洞
total === 0 时直接将状态设为 paid,可能导致空订单被标记为已支付。
应区分"空订单"和"总价为 0 的订单"。

事务重写注意事项:
1. 使用数据库事务包裹整个流程
2. 先锁定用户余额行(SELECT FOR UPDATE)
3. 在事务内完成:余额检查 → 扣款 → 订单状态更新
4. 全部成功后提交,任何一步失败则回滚

2.3 调试过程

场景一:回答太泛,未聚焦核心问题

arduino 复制代码
现象:AI 给出了通用性分析,但没有深入具体的状态流转分支。
原因:问题描述过于开放,AI 不知道你关心的侧重点。
排查:检查原始问题是否包含明确的输出格式要求。
修复:收紧焦点,加上具体指令,如"重点说明 cancelled 和 returned 的区别
     以及 process 步骤中的余额检查顺序是否有问题"。

场景二:Ask 模式下 AI 拒绝给出代码

arduino 复制代码
现象:你问"怎么重构这个函数",AI 只提供了文字建议。
原因:Ask 模式的预期行为就是只给分析不给代码修改------这是安全设计。
排查:确认对话面板顶部当前模式是否为 Ask。
修复:如需生成代码,切换到 Craft 模式,或将需求改为"请帮我写一个重构方案(伪代码)"。
      伪代码不算写文件,Ask 模式通常会提供。

场景三:@file 引用后 AI 似乎没看到文件

less 复制代码
现象:提问 @file order.ts 后,AI 的回复和文件内容无关。
原因:
  1. 文件路径不匹配,AI 没找到该文件。
  2. 光标放错位置------@ 引用必须在对话输入框内手动输入。
排查:
  1. 检查文件是否当前在 CodeBuddy 项目中(IDE 左侧文件树中可见)。
  2. 确认 @file 是通过弹出菜单选择的(而不是手动敲的路径字符串)。
修复:如果文件不在当前项目中,先用"文件 → 打开文件夹"打开包含该文件的目录。

三、Craft 模式:让 AI 动手写代码

3.1 原理讲解

Craft 模式是 CodeBuddy 的核心执行模式,AI 获得了文件读写终端命令执行权限。

工作原理

  1. 你描述需求,AI 解析后规划执行步骤。
  2. AI 依次执行:读取现有文件 → 生成代码 → 写入文件 → (可选)运行 ESLint/测试 → 修复错误。
  3. 每步文件变更都以 Diff 视图 展示,你可以逐条 Accept 或 Reject。
  4. 每次写操作前,CodeBuddy 会自动创建 Checkpoint(快照),出问题时可以一键回滚。

Craft 四件法宝

法宝 作用 使用时机
Diff 视图 查看所有变更细节 每次 Accept 前逐条审查
Checkpoint 一键回滚到修改前状态 改坏了、改错了
@ 引用 限定 AI 操作范围 防止 AI 修改不该动的文件
否定约束 告诉 AI 不要做什么 "不要改 Props 接口""不要引入新依赖"

3.2 示例一:从零创建登录组件

完整可运行的代码

操作步骤

  1. 切换到 Craft 模式。

  2. 确保项目使用 TailwindCSS(如果没有,先用 @file 引用配置文件)。

  3. 在对话框中输入:

    diff 复制代码
    @folder src/components
    请创建 LoginForm 组件,要求:
    - Props:onSubmit(email: string, password: string) => void
    - 包含邮箱输入框、密码输入框、提交按钮
    - 校验:邮箱格式(正则)、密码至少 6 位
    - 错误信息红色显示在对应输入框下方
    - 使用 TailwindCSS 样式
    - 使用 TypeScript
    - 创建后运行 ESLint 检查

AI 执行过程

vbnet 复制代码
Step 1: Write → src/components/LoginForm.tsx        ← 写入组件文件
Step 2: Bash  → npx eslint src/components/LoginForm.tsx  ← ESLint 检查
Step 3: 如果 ESLint 0 error → 完成
        如果有 error → AI 自动修复 → 再次检查
Step 4: Checkpoint 自动保存

预期输出文件src/components/LoginForm.tsx):

typescript 复制代码
import { useState, type FormEvent } from 'react';

interface LoginFormProps {
  onSubmit: (email: string, password: string) => void;
}

interface FormErrors {
  email?: string;
  password?: string;
}

const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

export function LoginForm({ onSubmit }: LoginFormProps) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState<FormErrors>({});

  const validate = (): boolean => {
    const newErrors: FormErrors = {};

    if (!email.trim()) {
      newErrors.email = '请输入邮箱地址';
    } else if (!EMAIL_REGEX.test(email)) {
      newErrors.email = '邮箱格式不正确';
    }

    if (!password) {
      newErrors.password = '请输入密码';
    } else if (password.length < 6) {
      newErrors.password = '密码至少需要 6 位字符';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    if (validate()) {
      onSubmit(email, password);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="w-full max-w-md space-y-4">
      <div>
        <label htmlFor="email" className="block text-sm font-medium text-gray-700">
          邮箱
        </label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          className={`mt-1 block w-full rounded-md border px-3 py-2 shadow-sm
            focus:outline-none focus:ring-2 focus:ring-blue-500
            ${errors.email ? 'border-red-500' : 'border-gray-300'}`}
          placeholder="请输入邮箱"
        />
        {errors.email && (
          <p className="mt-1 text-sm text-red-600">{errors.email}</p>
        )}
      </div>

      <div>
        <label htmlFor="password" className="block text-sm font-medium text-gray-700">
          密码
        </label>
        <input
          id="password"
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          className={`mt-1 block w-full rounded-md border px-3 py-2 shadow-sm
            focus:outline-none focus:ring-2 focus:ring-blue-500
            ${errors.password ? 'border-red-500' : 'border-gray-300'}`}
          placeholder="请输入密码"
        />
        {errors.password && (
          <p className="mt-1 text-sm text-red-600">{errors.password}</p>
        )}
      </div>

      <button
        type="submit"
        className="w-full rounded-md bg-blue-600 px-4 py-2 text-white
          hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500
          focus:ring-offset-2"
      >
        登录
      </button>
    </form>
  );
}
验收步骤
  1. 在 Diff 视图中审查所有变更,确认:
    • Props 接口未丢失属性。
    • 错误提示文案是中文。
    • 校验逻辑完整(空值 + 格式)。
    • 没有引入额外依赖。
  2. 全部确认后,点击 Accept 保存。

3.3 示例二:Bug 修复流程

场景

运行 npm run dev 后终端报错 TypeError: Cannot read properties of undefined

操作步骤

  1. 在 CodeBuddy 内置终端中重新执行 npm run dev,确保错误输出完整。

  2. 切换到 Craft 模式。

  3. 输入:

    less 复制代码
    @terminal
    @file src/hooks/useUsers.ts
    @file src/components/UserList.tsx
    请分析终端报错,找出根因并修复。

预期执行过程

vbnet 复制代码
Step 1: Read → 阅读 useUsers.ts 和 UserList.tsx
Step 2: Bash → grep 查找相关引用点
Step 3: Edit → 修复空值访问(添加 ?. 或 if 守卫)
Step 4: Bash → npm run dev → 验证通过

3.4 调试过程

场景一:AI 修改了不该动的东西

vbnet 复制代码
现象:你让 AI "重构校验逻辑",结果它连 Props 接口也改了,
     onSubmit 签名从 (email, password) 变成了 (data: LoginData)。

排查步骤:
Step 1: Diff 视图审查 → 发现接口变更。
Step 2: 点击 Checkpoint → 选择修改前的快照 → 一键回滚。
Step 3: 重新发送指令:"重构校验逻辑,但不修改 LoginFormProps 接口,
        保持 onSubmit(email, password) 签名不变"。

教训:"不要改 X" 的否定约束往往比 "要做 Y" 的正面指令更精确。

场景二:ESLint 报错后 AI 修复循环

markdown 复制代码
现象:AI 写完代码后 ESLint 报错,AI 自动修复,又引入新错误,陷入循环。
原因:ESLint 规则与 AI 的代码风格冲突。
排查:
  1. 阅读 ESLint 输出,确认冲突的规则。
  2. 如果冲突规则是项目必需的,将具体规则告知 AI。
修复:输入"ESLint 规则要求:函数必须有返回类型注解、禁止 any 类型、
      使用单引号。请按这些规则重新生成代码"。

场景三:终端命令执行失败

markdown 复制代码
现象:AI 执行 npm run dev 时报错 "command not found: npm"。
原因:CodeBuddy 的内置终端环境变量可能不完整。
排查:
  1. 在 CodeBuddy 终端中手动执行 npm --version,确认 npm 是否可用。
  2. 如果不可用,检查 Node.js 安装和 PATH 配置。
修复:
  - Windows:重启 CodeBuddy 后再次尝试。
  - macOS/Linux:确认 ~/.zshrc 或 ~/.bashrc 中有正确的 PATH 配置。

四、Plan 模式:先规划再执行

4.1 原理讲解

Plan 模式是面向不确定性高、影响面广的任务而设计的。

工作原理

  1. 需求澄清阶段:AI 不会直接动手,而是反过来提问。通过多轮问答消除需求歧义。
  2. 方案制定阶段:AI 输出技术方案(数据模型、API 设计、模块拆分),你审阅修改。
  3. 方案确认阶段:确认无误后切换到 Craft 模式执行。
  4. 逐步实施:按方案分阶段执行,每阶段完成后验证。
  5. 归档复用 :方案文档保存在 .codebuddy/ 目录,下次类似需求可直接参考。

何时使用 Plan(vs Craft)

维度 Plan Craft
清楚要做什么? 不太清楚 非常清楚
涉及模块数量 3 个以上 1-2 个
有前后端协作? 没有
失败损失 大(重构/迁移) 小(Checkpoint 可回滚)

经验公式:说不清 → Plan / 怕改坏 → Craft / 查东西 → Ask

4.2 示例:电商优惠券功能规划

完整可运行的场景

操作步骤

  1. 切换到 Plan 模式。

  2. 输入:

    复制代码
    我们是一个电商平台,需要加上优惠券功能。
    支持的优惠券类型:固定金额减免、百分比折扣。
    每张优惠券可以有使用次数限制。
    请先做需求澄清。

阶段一:需求澄清

AI 会逐条提问:

makefile 复制代码
Q1: 一个订单可以使用多张优惠券吗?还是一单一券?
Q2: 优惠券有最低消费门槛吗(如满 100 减 20)?
Q3: 优惠券能和会员折扣叠加吗?
Q4: 优惠券需要设置过期时间吗(绝对时间 or 领取后 N 天)?
Q5: 优惠券是全场的还是只能用于特定商品/分类?

你的回答

复制代码
每单只能用一张优惠券。
满 100 元可用。
不能和会员折扣叠加(选最优的那个)。
需要设置过期时间(绝对时间)。
全场通用(第一期不做商品/分类限制)。

阶段二:方案制定

AI 输出技术方案:

markdown 复制代码
# 优惠券功能技术方案

## 数据模型

### coupons 表(优惠券模板)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 主键 |
| type | enum('fixed','percent') | 优惠券类型 |
| value | decimal | 面额(固定金额 / 百分比) |
| min_amount | decimal | 最低消费门槛 |
| max_uses | int | 全局最大使用次数 |
| used_count | int | 已使用次数 |
| expired_at | datetime | 过期时间 |
| is_active | boolean | 是否启用 |

### user_coupons 表(用户领取记录)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 主键 |
| user_id | int | 用户 ID |
| coupon_id | int | 优惠券 ID |
| used_at | datetime | 使用时间(null=未使用) |

## API 设计

GET  /api/coupons/available   → 获取可用优惠券列表(带条件筛选)
POST /api/coupons/apply       → 应用优惠券(计算折后价)

## 实施阶段

Phase 1: 数据模型 + CRUD(管理后台)
Phase 2: 优惠券使用逻辑(下单时计算优惠)
Phase 3: 前端展示(优惠券列表 + 使用入口)
Phase 4: 过期检查(定时任务清理过期券)

阶段三:方案确认

你审阅后提出修改:

lua 复制代码
在 coupons 表加 is_active 字段很好。
user_coupons 也要加一个 status 字段(unused / used / expired)。
Phase 2 要补充:优惠券和会员折扣不能叠加时的选择逻辑。

AI 更新方案后,你确认 → 切换到 Craft 模式执行。

4.3 跑偏纠偏指南

偏差类型 表现 纠偏方法
需求理解错 AI 方案和你的预期差很远 回到阶段一,补充更多约束条件
方案太大 拆了 20 个 Phase 做不完 让 AI 按"最小可行产品"重新裁剪
实施有坑 执行中卡住、技术选型不对 暂停实施,修改 Plan 中对应部分,重新执行

Plan 模式最大优势:方案修改的代价几乎是零。如果直接写代码后发现路走错了,推倒重来的成本远高于花 10 分钟在 Plan 阶段修正方案。

4.4 调试过程

场景一:Plan 模式下 AI 直接开始写代码

markdown 复制代码
现象:你在 Plan 模式下提需求,AI 没有做需求澄清,直接创建了文件。
原因:
  1. 模式切换未生效------当前实际处于 Craft 模式。
  2. 需求描述过于具体,AI 判断"不需要规划"就直接执行了。
排查:
  1. 确认对话面板顶部模式显示为 "Plan"。
  2. 问题开头加一句"先做需求澄清,不要直接写代码"。
修复:新建对话,重新选择 Plan 模式,用更开放的方式描述需求。

场景二:AI 的澄清问题不够深入

arduino 复制代码
现象:AI 只问了 1-2 个表面问题就开始出方案。
原因:你的初始描述已经比较完整,AI 觉得自己理解了。
排查:
  如果方案中确实遗漏了关键点,说明 AI 没有真正理解。
修复:主动补充:"关于 X 方面,我还没有想清楚,请你多问几个问题帮我理清思路"。

五、组合拳:一天的标准工作流

三种模式不是孤立的,把它们组合成一条有节奏的编码流水线:

makefile 复制代码
09:00 Ask  → "useEffect deps 数组怎么写?"           → 学习/理解
09:30 Ask  → "方案 A vs 方案 B?"                     → 决策
10:00 Plan → "按方案 B 规划重构"→ Phase 1/2/3 → 确认   → 规划
10:30 Craft→ Phase 1 执行 → Diff 审查 → Accept          → 编码
11:00 Craft→ Phase 2 执行 → 测试 3 失败 → AI 修复 → 通过 → 编码+修复
11:30 Ask  → "还有什么可以优化的?"                    → 复盘/验证

核心节奏:Ask 评估 → Plan 规划 → Craft 执行 → Ask 验证


六、高效习惯总结

习惯 说明
Diff 必看 Accept 前逐条审查每一处变更
迭代推进 把大任务拆成小目标,每完成一个就验证
Checkpoint 兜底 重大修改前确认有快照,出问题秒回滚
否定约束 "不要改 X"比"做 Y"更精准
一个对话一主题 避免多个不相关任务混在同一个对话里

七、快捷键速查

功能 Windows macOS
打开对话面板 Ctrl + Shift + V Cmd + Shift + V
内联编辑 Ctrl + I Cmd + I

八、小结

复制代码
Ask  → 只问不做:查 API、看代码、做决策。
Craft→ 直接动手:写代码、修 Bug、跑终端。Checkpoint 保底。
Plan → 先想后做:新功能、迁移、协同。零成本改方案。

组合拳:Ask 评估 → Plan 规划 → Craft 执行 → Ask 验证
相关推荐
CodeSheep5 小时前
胡彦斌都开始苦修Vibe Coding,还上架App Store,都卷到编程来了吗?
前端·后端·程序员
AskHarries6 小时前
一个项目值不值得抄
程序员
Lkstar21 小时前
高级提示技巧:Few-shot、Chain-of-Thought、自一致性——让大模型推理能力翻倍
程序员·llm·ai编程
lazyboon21 小时前
写 Cron 表达式时,我最怕的不是写错,而是“以为自己写对了”
程序员
DyLatte1 天前
很多人把坚持,误以为成长
前端·后端·程序员
京东云开发者1 天前
开放原子开源基金会新增孵化项目(2026年5月)
程序员
爱勇宝1 天前
写给年轻程序员:别急着证明自己,也别太早放过自己
前端·后端·程序员
程序员cxuan1 天前
太顶了,ChatGPT 要和 Codex 搞一起了。
人工智能·后端·程序员
析数塔1 天前
编译两分钟,修改五秒钟:Zig构建系统重构解决的老问题
程序员·rust