# 更新日志

本项目的所有显著变更都记录在此文件中。

格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)，版本遵循语义化版本。

## [0.0.9] - 2026-10-21

### 修复

**`plan_question` 弹窗中文标点前显示方块（□）**：上游模型（agent-gateway / glm 系）偶发在流式 tool_call 参数里夹带 CR 控制符（`\r`）——会话 JSONL 证实同一模型纯文本/thinking 内容零污染，而 toolCall arguments 里有 130+ 处 CR，插在汉字与标点的 token 边界；JetBrains 侧把 CR 渲染成豆腐块。`plan_question` 现在在入口对 question/header/label/description 统一清洗 CR 与零宽字符（U+200B–U+200D、U+FEFF），弹窗选项、无 UI 回退文本、选择结果回传全链路干净；冒烟补 2 个用例。治本需在上游网关对工具参数做同等清洗（覆盖 edit 等其它工具）

## [0.0.8] - 2026-10-21

### 新增

- **`plan_question` 自由输入入口**：此前用户只能在 AI 预置的选项里单选，想表达选项之外的想法只能 Esc 取消后打字。现在选择列表末尾自动追加「✏️ 自行输入…」入口，选中后弹输入框自由作答（首尾空白裁剪）；输入为空或 Esc 返回选项列表，可改选预置项（镜像官方 question.ts 示例的交互）；AI 选项恰好与入口同名时复用为该入口、不重复追加。RPC/ACP 桥接下输入框走 `ui.input` 通道（JetBrains 弹窗 elicitation 表单），与 select 同级可用

## [0.0.7] - 2026-10-21

### 变更

- **包根入口薄壳**：`pi.extensions` 从 `./src/index.ts` 改为 `./index.ts`（对齐 `@wbline/pi-acp-jetbrain` 的做法），pi 中插件显示从 `@wbline/pi-plan-build:src` 变为干净的 `@wbline/pi-plan-build`——`:src` 后缀来自扩展路径首段目录；`main` 同步指向 `./index.ts`，`files` 补列 `index.ts`（否则发布包缺薄壳入口），`tsconfig.json` include 纳入薄壳。实现代码零改动，`src/` 结构不变

## [0.0.6] - 2026-10-20

### 新增

- **`plan_task revise`：build 中途修订计划的专用通道**：此前 build 模式下计划文件受保护、agent 又无法自行切回 plan（`new` 在已有计划时报错），修订单行计划要经历"被拦 → 求用户手动 /plan → 改文件 → 手动切回"的循环。现在 `plan_task revise` 弹确认门切回 plan（对称于 `new` 的 build 确认门；无 UI 时一致跳过），改完 `plan_exit` 重新提请审批；分步执行进行中仍须先 `plan_step_control cancel`；守卫阻断文案与 include 成功提示同步指向该通道
- **验证问询改到下一条消息**：`awaiting_validation` 结算后不再在回合刚结束（用户还没验证）就弹"验证通过？"——改为监听 `input` 事件，在用户下一次空闲发消息时确认一次；未通过不自动重问，重新结算（`plan_set_outcome` / `plan_complete`）时重置。流式插话（steer/followUp）、斜杠命令、extension 触发不询问

### 变更

- **守卫范围显式化**：只守卫 pi 内置工具与 JetBrains IDE 桥接工具（ide_ 前缀）——内置 8 个全量显式分类（bash/powershell 归入执行面、read/find/grep/ls 归入只读，不再落入管辖外兜底），事后对账武装条件统一为 execution 类别；管辖外仅剩第三方/未知扩展与 MCP 工具。codemode 的嵌套工具调用经 pi 同一 `tool_call` 管线（已核对 1.3.0 源码 `ctx.executeTool` → `_executeNestedToolCall`），守卫无旁路
- **只读动词白名单收紧**：`generate` 从动词表移除（未来 `generate_code` 类写方法被误放行的风险太高），已确证的纯生成方法（`_generate_psi_tree` / `_generate_inspection_kts_api` / `_generate_inspection_kts_examples`）改按方法后缀精确放行，未知 `generate_*` 保守按变更通道；新增**写动词优先**——名字含 `write / apply / create / edit / delete / rename / move / save / format` 任一段时，即使同时命中只读动词也按变更通道（堵 `apply_diff`、`move_files` 类组合名）
- **IDE 执行面工具与 shell 同层待遇**：终端命令、运行配置、构建、SQL 执行、inspection KTS、取消查询等进程执行类方法（目标不可提取、能力与 shell 等价）不再按文件变更通道硬拦——plan 模式提示词约束（`planReadonly` 同步点名执行类命令仅限只读调研，安装依赖/UPDATE 等写操作留到 build），build 模式放行并纳入事后对账；桥接注解与 `toolGuard.mutating` 仍可强制拦回。此前 plan 模式硬拦 IDE 执行通道却放行 bash，拦不住想绕的、只摩擦守规矩的模型
- **无 UI（print/json）行为一致化**：`plan_task new` 入口询问与 `plan_exit` 审批门在无交互 UI 时一致跳过——`plan_exit` 此前一律视为"留在 plan"，agent 永远拿不到实现授权（脚本场景死锁），现在自动选「本会话连续实现」并在结果中显式标注；审批对话框被取消（Esc）与主动选择「留在 plan」区分开，取消时模型会向用户询问意图
- **工具描述压缩并支持双语**：7 个工具的描述与参数描述集中到 `tools/descriptions.ts`，按 `lang` 设置在注册时选用 zh/en（此前只有中文）；工作流规则交给 context 注入，描述只保留动作语义与硬约束。语言切换后需 /reload 生效（注册时固化）
- **fromFile 恢复不再产生无主副本**：`plan_task new fromFile` 恢复 plans 目录内的文件时原地接管（不再复制出新文件）；恢复外部文件时复制到 canonical 路径并在原文件文末追加「已接管」状态块（`list` 可见），避免两份同内容文件分不清哪份有效
- **scope 注入截断**：context 注入的计划范围超过 2000 字符时截断并提示"完整内容以计划文件为准"，长计划（尤其 fromFile 全文恢复）不再无限膨胀上下文；同一内容截断结果稳定，不影响前缀缓存命中
- **expectedAttached 不匹配的报错可自愈**：从"请重读 show 后重试"改为同时给出当前 seq——无副作用动作可直接改用新 seq 重试，include 等范围类动作仍建议先 show 核对，减少一轮往返

### 修复

- **写入目标与计划路径统一做 symlink 解析**：此前只有计划路径过 `realpathLike`，写入目标不做——项目路径含 junction/symlink 成分（Windows 目录联接、macOS `/tmp`、WSL）时，经链接路径写计划文件会被判为「目标 ≠ 计划文件」误拦（plan 流程死锁）。`realpathLike` 同时升级为逐级向上解析（兑现"最远存在父目录"的注释承诺，嵌套新目录不再漏解析）
- **apply_patch 补齐 unified diff 的源路径提取**：git rename diff 的旧路径只出现在 `--- a/<path>`（改名+改内容）或 `rename from <path>`（纯改名，无 `---`/`+++` 行），此前只扫 `+++`——build 模式下用 unified diff rename 可将受保护的计划文件改名带走（保护绕过）。现在 `---`/`+++` 成对解析（正文 `-`/`+` 开头的行不成对不误提）、`rename from/to` 补齐、`/dev/null` 占位头过滤

- **IDE 桥接工具目标提取不再写死 webstorm 产品名**：`extractMutationTargets` 改按方法后缀匹配（`_create_new_file` / `_reformat_file` / `_apply_patch` / `_rename_refactoring`），产品段任意（webstorm / intellij / pycharm / goland / rider / …）。此前 IDEA、PyCharm 等产品的同类工具全部落入「波及面无法验证」：build 模式靠事后对账兜底尚可，plan 模式则连写计划文件本身都被拦——IntelliJ-first 桥接下原生写通道被禁用，plan 流程会死锁在 `plan_exit` 的写盘校验。`_rename_refactoring` 显式排除在 `pathInProject` 参数形状兜底之外（语义重命名波及全部引用处，任何产品下都不可验证）；未知方法名只要按约定带 `pathInProject` 即可精确提取，认不出的仍返回 undefined（宁可误拦不可放行）。分类层面本就只认 `ide_` 前缀，不受影响

## [0.0.5] - 2026-10-17

### 新增

- 对称确认门：build 模式调用 `plan_task new` 先弹窗确认切到 plan 模式（拒绝则保持 build 并提示可直接实现）；plan 模式守卫拦截文件修改时也弹确认门——用户确认即切 build 放行本次修改，拒绝则本回合只拦不再问（agent_settled 重置），守卫文案同步补上入口闸路径
- 连跑授权：`plan_step_control start` 支持 `advance: "all"`——一次批准连跑剩余步骤，`plan_step_complete` 后自动推进下一步（注入切换为连跑提示词），遇 `plan_set_outcome` 记录的阻塞才停；默认仍为每步等批准
- 历史计划可见可续：`plan_task list` 扫描 plans 目录列出历史计划（标题 + 终态归档结局 + 日期，未收尾标注）；`plan_task new` 支持 `fromFile` 恢复既有计划文件（复制内容到 canonical 路径并标记已写盘），换会话后可接着干
- 入口询问（与 `plan_exit` 出口审批对称）：plan 模式且无当前计划时，`plan_task new` 在用户未明确要求规划（未传 `planningRequested: true`）时先弹窗让用户选「创建计划 / 直接执行」——选直接执行自动切 build 并放弃建计划，取消则不建计划等待指示；无 UI（print/json）环境跳过询问维持原行为。`taskSelection` 提示词、工具描述、README 同步，冒烟补 3 个用例；计划标题约束"标题内不用冒号"（避免与弹窗标题分隔符撞车）
- 终态归档：计划收尾（`plan_complete` / 分步最后一步完成 / `awaiting_validation` 验证通过确认）或放弃（`plan_task abandon` / 菜单）时，在计划文件文末追加状态块——`> **状态：已完成/已放弃**（本地日期）` + 摘要或原因，plans 目录的历史文件单看即可知结局。append-only 不改已审阅内容；集中在 `engine.apply` 一处覆盖全部收尾调用点，文件为空或写失败静默跳过（状态为准）；归档结果透出到工具结果（`lastArchive` 每次 apply 重置），模型可如实汇报。冒烟补 2 个用例

### 变更

- 步骤进度符号统一为等宽中文标签：侧栏 widget 的 `○◉◐●↷`（几何圆圈 + 箭头混排，CJK 环境下宽度歧义）改为 `[待办]/[就绪]/[进行]/[完成]/[跳过]`；状态栏的 `▶`/`⏸`（易被 emoji 化渲染）改为分数 + `（暂停）` 后缀
- 模式切换不再发对话通知：`mode.set` 转移不再携带 `note`（notify 在 IDE/ACP 桥接下渲染成对话消息，每次 /plan、/build、Alt+M 都刷屏）；模式反馈由状态栏徽标实时承载，plan_exit 审批与入口闸"直接执行"的对话框本身就是反馈
- 审批对话框措辞统一为「动作，后果」风格、去掉括号：入口「创建计划，审阅后再实现 / 直接执行」，出口「本会话连续实现，完成后验证 / 分步实现，每步等待批准 / 留在 plan 模式，暂不实现」；出口标题改为「请选择计划的实现方式：〈标题〉」，与入口「请选择新任务的处理方式：〈标题〉」句式对称

## [0.0.4] - 2026-10-16

### 文档

- README 面向 [pi.dev/packages](https://pi.dev/packages) 画廊首页重写：一句话价值主张 + plan→审批→build 全流程图、"它解决什么问题"对照表、核心机制速览、设置表格化，技术细节（工具表/权限边界/设计取舍）完整保留后置。无代码变更

## [0.0.3] - 2026-10-16

借鉴 pi-plan-build 参考项目的两道硬机制（时序闸、事后对账），并对双模式链路做四轮深入排查（模式转移、守卫、对账、持久化、提示词、工具契约、测试卫生）。

### 新增

- 时序硬闸：最近一条 assistant 消息里 `plan_task` 结构转移（new/include/update/exclude/abandon）未收尾时，文件变更与生命周期工具一律先等转移收尾——"生命周期转移必须先于依赖它的写入/命令单独完成"从提示词约束升级为硬拦截（借鉴参考项目的 dependent-actions 闸）
- 事后对账：build 模式放行波及面无法验证的变更工具（IDE 终端命令、bash/powershell 等）前记录计划文件内容基线，agent_settled 结算时文件被改动即警告提示核对（借鉴参考项目的 reconciliation，简化为 sha256 内容摘要比对）；用户关闭 `protectPlanFile` 时不武装；新会话（startup/new）不继承上一会话基线，防误报
- 工具守卫去写死名单：`tool_call` 时经 `pi.getAllTools()` 动态读取工具注解（`readOnlyHint`），无注解时按名字动词约定分类（`get/search/list/…` 只读放行，其余 `ide_*` 保守视为变更通道——plan 阻断 / build 放行）；设置文件 `toolGuard.readonly / mutating` 可强制覆盖，新增 IDE 变更工具不再漏拦

### 变更

- **模式转移收紧（堵死角）**：`plan_task new` 仅限 plan 模式——此前 build 模式可创建计划但 `plan_exit` 拒绝执行，计划成孤儿；分步执行进行中禁止切回 plan 模式——此前会产生"只读约束 + 实现第 N 步"的自相矛盾注入
- 显示名统一为小写 `plan` / `build`：状态栏徽标 `📋 plan` / `🔨 build`、命令/工具描述、守卫阻断文案、zh/en 提示词、工具 label、`plan_exit` 审批选项与 README 全部同步；ID/协议层本就保持小写，不受影响

### 修复

- **`expectedAttached` 契约与实现不符**：文档与工具描述声称"必须带、过期即拒"，代码却允许不传跳过校验——现严格强制（new 必须显式 null，其余必须等于当前 seq）
- **`exec.start` 清遗留结局**：blocked 等结局记录后重新分步实现，旧结局与新执行并存造成矛盾注入——开始执行即失效
- **崩溃窗口状态不一致**：`plan_exit` 先开执行后切 build，两段持久化之间崩溃会留下"plan 模式 + 执行态"的转移不可达组合——交换顺序，且 `decodeState` 拒绝该组合（恢复回退到最近合法条目）
- **corrupt 状态下事件 handler 不再抛未捕获异常**：session_start 默认模式应用与验证通过回调改用 tryApply，错误以通知呈现
- **提示词动作名纠错**：zh/en `taskBoundary` 引用不存在的 `discussion` 动作（参考项目词汇残留）——实际动作为 `exclude`
- **`plan_task include` 在 build 模式的指引**：成功消息补充"计划文件受保护：先 /plan 切回再修订"——此前模型直接改计划文件会被守卫拦截，陷入循环
- **冒烟测试目录卫生**：smoke 此前用真实项目 cwd，每次运行在项目 `.pi/agent/plans/` 残留空占位文件——改用临时目录并在收尾清理

## [0.0.2] - 2026-10-16

0.0.1 发 npm 后的全部修复与改进。

### 新增

- `/plan`、`/build` 直切模式命令（保留 `/plan-build` 菜单与 Alt+M）——IDE（ACP）桥接层 `setSessionMode` 按命令名调用，JetBrains AI 聊天的 Plan/Build 模式选择器依赖这两个命令
- 守卫覆盖 IDE 变更通道：`ide_webstorm_apply_patch`（解析 codex/unified diff 目标）、`ide_webstorm_create_new_file`（`pathInProject`）、`ide_webstorm_reformat_file`（`files[]`）；`ide_webstorm_rename_refactoring` 波及面无法验证，Plan 模式一律阻断
- 持久化状态附带 `selectedMode` 字段，供 IDE 模式选择器同步显示

### 修复

- **IDE 模式切换不生效**：状态条目类型 `pi-plan-build:state` → `pi-plan-build-state`，与 pi-acp-jetbrain 桥接层契约对齐；此前 IDE 切模式报 "not installed"、选择器恒显示 build
- **空计划可通过 `plan_exit` 审批**：`nextPlanPath` 分配即建占位空文件使存在性检查恒真；现校验内容非空
- **Windows 路径大小写绕过/误拦**：win32 下守卫路径比较大小写不敏感
- **`acknowledge` 语义**：指认用户声明的已完成步不再要求先 `start`（新转移 `exec.step.ack`，ready/active 皆可）
- **`revise` 先改文件后校验**：改为先过状态机校验再同步计划文件；文件无对应标记时如实回报未同步
- **`plan_task new` 失败留孤儿占位文件**：失败路径回收空占位
- **损坏状态判定过宽**：仅"最新"状态条目损坏才进入只读保护，更早坏条目之后有合法状态则正常恢复
- **`defaultMode` 漏 `/new`**：`startup` 与 `new` 会话都应用默认模式；resume/fork/reload 尊重恢复出的状态

### 变更

- 显示名统一为首字母大写 `Plan` / `Build`（状态栏徽标 `📋 Plan` / `🔨 Build` 与 IDE 选择器一致）；ID/协议层保持小写（`mode: "plan"`、`/plan`、ACP modeId）
- `FILE_MUTATION_TOOLS` 收敛为 pi 1.3.0 实际存在的 `edit/write`，移除不存在的 `replace/insert/undo_last_change`

### 文档

- README：命令表补充 `/plan` `/build` 及 IDE 依赖说明；权限边界更新为原生 + IDE 双通道语义

## [0.0.1] - 2026-10-16

npm 首发（`pi install npm:@wbline/pi-plan-build`）。针对 pi 1.3.0 的 v2 重写。

### 新增

- Plan/Build 双模式：只读规划 → 显式审批（本会话实现 / 分步实现 / 留在 Plan）→ 分步或整段实现 → 验证闭环
- 集中状态机：单一不可变 `State` + 唯一 `transition()` 入口，纯函数单测覆盖；状态经 `appendEntry` 持久化、会话恢复
- 7 个工具全模式常驻（prompt cache 友好，模式合法性由执行时守卫保证）：`plan_task` / `plan_exit` / `plan_step_control` / `plan_step_complete` / `plan_complete` / `plan_set_outcome` / `plan_question`
- 计划文件落当前项目 `.pi/agent/plans/`，语义化命名 `<创建时刻yyyyMMdd-HHmm>-<标题slug>.md`，同名自动递增
- 交互：唯一命令 `/plan-build` 菜单（TUI 自绘组件 / RPC select 降级）、Alt+M 快捷键切换、底部状态栏模式徽标（📋/🔨）与分步进度 widget
- 权限守卫：`tool_call` 前拦截，路径归一化（`@`/`~`/相对路径）+ symlink 解析；Plan 只许写计划文件、无路径阻断；Build 保护已写盘计划文件
- 上下文锚定注入：计划块作为 user 消息插在最后一条真实用户消息之前，位置与内容稳定以命中前缀缓存
- `awaiting_validation` 结算后自动弹"必需验证是否通过？"确认（once 防重放）
- 提示词中/英双语（设置 `lang`），设置文件 `~/.pi/agent/pi-plan-build.json`

### 设计取舍

- 收敛为单一命令 + 交互菜单（不再有 `--pb-plan`/`--pb-build` 启动 flag、Alt+P 快捷键）
- 原生 UI（`setStatus`/`setWidget`/`ui.custom`），不接管 composer，与其它扩展零冲突
- 砍掉跨会话干净交接：换会话重新规划，计划文件本身可复用
- bash/powershell 不做命令分类沙箱，只读边界由提示词约束（诚实标注）
