# 设计文档：模式简化与目标驱动修复循环

## 1. 背景

KCode 现有三种 Harness 模式（lite / standard / strict），外加手动验证循环（kd_verify_result → repair.ts）。存在两个问题：

1. **模式过多**：lite/standard/strict 三个模式用户难以区分，实际只区分为"要不要门禁"即可。
2. **验证循环手动**：用户需要自行运行验证命令、调用 kd_verify_result、等待 agent 修复、再次运行——反复切阶段，体验割裂。

## 2. 设计目标

1. 将 Harness 模式从三个简化为两个：quick（无门禁）和 normal（完整门禁）。
2. 新增 `/kd goal` 命令，完全取代 `kd_verify_result`：用户设目标，系统自动执行验证、修复、再验证直到通过或超限。
3. 不做向后兼容，直接重构。旧 run 在重构后需要重新创建。

## 3. 方案一：模式简化 — quick / normal

### 3.1 模式定义

| 维度 | quick | normal |
|---|---|---|
| 门禁 | 全部跳过 | 现有全部保留 |
| 阶段序列 | 无，直接 execute | discuss → plan → execute → verify |
| 适用场景 | typo 修复、简单字段校验、已知路径的快速改 | 新功能、复杂校验、SQL 修改、第三方对接 |
| 产物要求 | 无 | PLAN.md、EXECUTION.md、VERIFY.md |
| 提问门禁 | 跳过 | 保留（必须确认业务事实后方可编码） |
| 验证 | 无要求 | 必须记录验证证据 |

### 3.2 用户入口

用户必须显式指定模式：

```
/kd quick 修复采购订单保存校验的 typo
/kd normal 实现采购订单保存校验
```

无默认值，不自动推断。

### 3.3 实现变更

**类型层（types.ts）：**
```typescript
// 替换
export type KdHarnessMode = "quick" | "normal";
```

**阶段顺序：**
```typescript
export const MODE_PHASE_ORDER: Record<KdHarnessMode, KdPhase[]> = {
  quick: ["execute"],
  normal: ["discuss", "plan", "execute", "verify"],
};
```

**门禁层（gates.ts）：**
- `inspectGate` 遇到 `quick` 模式直接返回 `{ passed: true }`，跳过所有门禁检查。
- `quick` 模式不写入证据，不检查 evidence index，不创建阻断问题。

**CLI 入口：**
- 移除 `lite`/`standard`/`strict` 参数。
- 新增 `/kd quick <需求>` 和 `/kd normal <需求>` 入口。
- 旧有 `lite`/`standard`/`strict` 代码路径全部删除。

### 3.4 迁移

- 不做向后兼容。重构后旧 `lite`/`standard`/`strict` run 不再可读。
- 对应 `run-sanitizer.ts`、`mode-policy.ts` 中的旧模式处理逻辑全部移除。

## 4. 方案二：/kd goal — 目标驱动自动修复循环（取代 kd_verify_result）

### 4.1 概念

用户定义一个可验证的目标（如"npm run check 通过"），系统自动迭代执行。完全取代 `kd_verify_result` 工具。

### 4.2 流程

```
用户：/kd goal "npm run check 通过"
系统：
  ├─ 第 1 轮：自动执行 npm run check
  │   ├─ exitCode=0 → 完成，退出
  │   └─ exitCode≠0 → 记录失败证据 → 回到 execute → agent 修复 → 第 2 轮
  ├─ 第 2 轮：自动重新执行 npm run check
  │   ├─ exitCode=0 → 完成
  │   └─ exitCode≠0 → ...（最多 3 轮）
  └─ 超限 → 创建阻断问题，询问用户继续、回 plan 或停止
```

### 4.3 重用 repair.ts

`/kd goal` 不重写 repair.ts，而是调用其内部逻辑：

goal-loop.ts 负责：
- 接收用户目标文本
- 解析目标中的验证命令
- 自动执行验证命令（代替用户手动跑）
- 根据 exitCode 调用 repair.ts 的 `recordVerifyResult`（代替用户手动调 kd_verify_result）
- 若失败则循环，直到通过或超限

repair.ts 保留：
- attempts / maxAttempts 轮次追踪
- failure evidence 记录
- 阶段切换（verify → execute）
- 超限阻断问题创建

### 4.4 中断续跑

同一目标被中断后，再次 `/kd goal` 从上一次失败处继续，不从头开始：

- 读取 `run.repair.lastFailureEvidence` 和 `run.repair.lastFailureSignature`
- 复用上次的验证命令，跳过目标解析步骤
- 从当前轮次继续计数，不重置

### 4.5 目标解析

```
/kd goal npm run check 通过
/kd goal npm test 全部通过 且 lint 干净
/kd goal tsc --noEmit 无错误
/kd goal 所有测试通过
/kd goal 构建无错误
```

解析策略：
1. 尝试提取明确的验证命令（npm run check、tsc --noEmit 等）
2. 若只有自然语言描述，由 LLM 根据 package.json scripts 推断
3. 无法确定时提问用户

### 4.6 循环控制

| 参数 | 默认值 | 说明 |
|---|---|---|
| 最大修复轮次 | 3 | 达到后创建阻断问题 |
| 命令超时 | 120s | 单次执行超时则视为失败 |
| 失败签名去重 | 开启 | 相同失败签名不消耗轮次，直接阻断 |

### 4.7 状态持久化

复用现有 repair 字段，新增 `goal`：

```typescript
export interface RepairState {
  attempts: number;
  maxAttempts: number;
  lastFailureEvidence?: string;
  lastFailureSignature?: string;
  status: "idle" | "repairing" | "blocked";
  goal?: string;          // 新增：用户设定的目标
  updatedAt: string;
}
```

### 4.8 新增文件

**`src/harness/goal-loop.ts`**：
- `parseGoal(text)` → `{ command, condition } | null`
- `executeGoal(cwd, run, goal)` → 自动循环
- `resolveVerifyCommand(goal, projectDir)` → 从目标推断命令

**命令注册处（已有）**：
- 注册 `/kd goal` 命令，替换 `/kd-verify-result` 和 `kd_verify_result` 工具

### 4.9 用户界面

```
/kd goal "npm run check 通过"
→ [1/3] 执行 npm run check...
  [失败] exit 2
  [修复] 已切回 execute，agent 分析失败证据中...
→ [2/3] 重新执行 npm run check...
  [通过] ✓ 目标达成，已记录 evidence
```

### 4.10 清理

移除以下与 `kd_verify_result` 相关的内容：
- `kd_verify_result` 工具注册
- `/kd-verify-result` 命令
- `VerifyResultInput` / `VerifyResultOutcome` 接口（迁移到 goal-loop 内部）
- 提示词中关于 `kd_verify_result` 的指令

## 5. 边界情况

| 场景 | 处理 |
|---|---|
| 目标无法解析命令 | 提问用户"请指定要运行的验证命令" |
| 同一失败反复出现（签名相同） | 不消耗轮次，直接阻断 |
| quick 模式下使用 /kd goal | 允许，quick 只跳过门禁，自动修复仍然有效 |
| normal 模式下使用 /kd goal | 正常，通过后自动进入 verify |
| 用户中途打断 | 三轮阻断问题可选"继续/回 plan/停止" |
| 中断后再次 /kd goal | 从上一次失败处继续，不重置 |
| 旧 run（lite/standard/strict） | 不可读，需重新创建 |
