CodeBuddy 学习(2):对话模式深度使用
一、概述:三种模式的内在逻辑
1.1 为什么需要三种模式?
对话式 AI 编程的核心矛盾在于控制权分配------什么时候 AI 可以自主行动,什么时候必须等待你的确认。
| 场景 | 控制权归属 | 匹配模式 |
|---|---|---|
| 提问/学习/讨论 | 你完全掌控,AI 只输出信息 | Ask |
| 明确编码任务 | AI 直接执行,你逐条审查 | Craft |
| 复杂不确定任务 | AI 先规划,你确认后执行 | Plan |
三种模式不是并列选项,而是一条控制权逐级释放的递进路径:
Ask(只问不做)→ Craft(边做边调)→ Plan(先想后做)
核心原则:你越清楚要做什么,越可以交给 AI 直接执行;你越不确定,越应该先用 Ask/Plan 收敛思路。
二、Ask 模式:只读问答
2.1 原理讲解
Ask 模式的权限约束是最严格的------不允许修改任何文件、不允许执行任何命令。
工作原理:
- AI 接收你的问题,同时从当前打开的文件和项目中收集上下文。
- 通过
@引用可以在当前消息中注入特定文件的完整内容。 - AI 基于上下文生成回复,但所有回复都是纯文本,不会产生任何副作用。
- 回复内容中可能包含代码片段,但需要你手动复制到文件。
适用场景:
| 场景 | 你问什么 | 得到什么 |
|---|---|---|
| 学习 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 };
操作步骤
-
在 CodeBuddy 中打开
order.ts。 -
切换到 Ask 模式。
-
在对话框中输入:
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 获得了文件读写 和终端命令执行权限。
工作原理:
- 你描述需求,AI 解析后规划执行步骤。
- AI 依次执行:读取现有文件 → 生成代码 → 写入文件 → (可选)运行 ESLint/测试 → 修复错误。
- 每步文件变更都以 Diff 视图 展示,你可以逐条 Accept 或 Reject。
- 每次写操作前,CodeBuddy 会自动创建 Checkpoint(快照),出问题时可以一键回滚。
Craft 四件法宝:
| 法宝 | 作用 | 使用时机 |
|---|---|---|
| Diff 视图 | 查看所有变更细节 | 每次 Accept 前逐条审查 |
| Checkpoint | 一键回滚到修改前状态 | 改坏了、改错了 |
| @ 引用 | 限定 AI 操作范围 | 防止 AI 修改不该动的文件 |
| 否定约束 | 告诉 AI 不要做什么 | "不要改 Props 接口""不要引入新依赖" |
3.2 示例一:从零创建登录组件
完整可运行的代码
操作步骤:
-
切换到 Craft 模式。
-
确保项目使用 TailwindCSS(如果没有,先用
@file引用配置文件)。 -
在对话框中输入:
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>
);
}
验收步骤
- 在 Diff 视图中审查所有变更,确认:
- Props 接口未丢失属性。
- 错误提示文案是中文。
- 校验逻辑完整(空值 + 格式)。
- 没有引入额外依赖。
- 全部确认后,点击 Accept 保存。
3.3 示例二:Bug 修复流程
场景
运行 npm run dev 后终端报错 TypeError: Cannot read properties of undefined。
操作步骤:
-
在 CodeBuddy 内置终端中重新执行
npm run dev,确保错误输出完整。 -
切换到 Craft 模式。
-
输入:
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 模式是面向不确定性高、影响面广的任务而设计的。
工作原理:
- 需求澄清阶段:AI 不会直接动手,而是反过来提问。通过多轮问答消除需求歧义。
- 方案制定阶段:AI 输出技术方案(数据模型、API 设计、模块拆分),你审阅修改。
- 方案确认阶段:确认无误后切换到 Craft 模式执行。
- 逐步实施:按方案分阶段执行,每阶段完成后验证。
- 归档复用 :方案文档保存在
.codebuddy/目录,下次类似需求可直接参考。
何时使用 Plan(vs Craft):
| 维度 | Plan | Craft |
|---|---|---|
| 清楚要做什么? | 不太清楚 | 非常清楚 |
| 涉及模块数量 | 3 个以上 | 1-2 个 |
| 有前后端协作? | 有 | 没有 |
| 失败损失 | 大(重构/迁移) | 小(Checkpoint 可回滚) |
经验公式:说不清 → Plan / 怕改坏 → Craft / 查东西 → Ask
4.2 示例:电商优惠券功能规划
完整可运行的场景
操作步骤:
-
切换到 Plan 模式。
-
输入:
我们是一个电商平台,需要加上优惠券功能。 支持的优惠券类型:固定金额减免、百分比折扣。 每张优惠券可以有使用次数限制。 请先做需求澄清。
阶段一:需求澄清
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 验证