@ghyper9023/pi-dev-workflow 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ [fix] 修复 当前目录 中的 问题: 1.gitcommit哈希dc0d3fa9d2362f4fb5026378e472510902dacd8e的修改,增加的grill换行修复,现在是(a)左边简短被阶段黑色字体提问 右边灰色字体提问内容,没截断,但也没换行 2.grill拦截左方向键返回上一题功能正常,但是他破坏了那种grill提问自定义输入式的和dev-*输入式的左方向键,输入式的左方向键应该是光标向左,而现在是返回上一题了,原来的ctrl+shift+左是正常的且考虑了避开输入式的左方向键应该是光标向左的冲突
2
+
3
+ **背景**:
4
+ - 输入:见代码上下文
5
+ - 预期行为:修复: 1.grll提问选项正确实现换行。 2.输入式的ui不能监听向左键,我的建议是grill提问环节的向左返回,统一改成和输入式一样的`ctrl+shift+左`来返回。
6
+ - 当前错误:请描述当前错误
7
+ **任务**:
8
+ 1. 不要仅仅消除报错(Suppress),要解决根本原因。
9
+ 2. 先读取相关代码和日志,诊断根因(多步推理,不要先给结论)。
10
+ 3. 提供至少一种修复方案,并说明为什么这样做。
11
+ 4. 编写测试用例复现该 Bug 并确认修复有效。
12
+ **输出**:提供 diff 和两句话的根因分析。
13
+ **约束**:只修 bug,不做重构;最小化改动;不要假设错误是微不足道的。
@@ -0,0 +1,274 @@
1
+ # Grill 左方向键冲突与选项描述显示修复 — 实施计划
2
+
3
+ ## 概述
4
+
5
+ 修复 `dc0d3fa9` 提交引入的两个回归问题:
6
+
7
+ 1. **左方向键破坏 Input 光标左移**:`uiInput` 中将裸 `←` 拦截为"返回",导致所有 `backable=true` 的输入场景中,左方向键的"光标左移"功能完全失效。
8
+ 2. **Grill 选项截断+description 显示混乱**:选项 label 被截断后,完整文本放入 `description` 字段,但 SelectList 的 description 列不换行,导致用户看到"左边简短被截断 + 右边灰色不换行"的混乱显示。
9
+
10
+ **用户明确要求**:grill 提问环节的向左返回统一改为 `Ctrl+Shift+←`(与输入式一致),输入式的左方向键恢复为光标左移。
11
+
12
+ ## 根因分析
13
+
14
+ ### Bug 1:左方向键冲突
15
+
16
+ **问题位置**:`extensions/ui-helpers.ts` 第 263-266 行
17
+
18
+ `dc0d3fa9` 提交在 `uiInput.handleInput` 中添加了:
19
+
20
+ ```typescript
21
+ // 左方向键 → 返回(优先于 Input 的光标左移)
22
+ if (backable && matchesKey(data, Key.left)) {
23
+ done(BACK_MARKER);
24
+ return;
25
+ }
26
+ ```
27
+
28
+ 这段拦截在 `input.handleInput(data)` 之前执行,Input 组件永远不会收到左方向键事件,导致:
29
+ - Input 内部的 `tui.editor.cursorLeft` 绑定(左方向键)永远不会被触发
30
+ - 用户无法在输入框中左移光标
31
+ - 影响范围:所有 `backable=true` 的 uiInput 调用(grill 自定义输入、dev-* wizard 输入等)
32
+
33
+ 同时,`extensions/grill-me-agent.ts` 第 671-674 行的 `showQuestionTUI.handleInput` 也拦截了左方向键:
34
+
35
+ ```typescript
36
+ // 左方向键 → 返回上一题
37
+ if (backable && currentIndex > 1 && matchesKey(data, Key.left)) {
38
+ done("__BACK__");
39
+ return;
40
+ }
41
+ ```
42
+
43
+ 两处拦截叠加,用户按 `←` 时:
44
+ - 在 SelectList 中 → 返回上一题(正确,因为 SelectList 不处理 left 键)
45
+ - 在 Input 中 → 返回上一题(错误,期望光标左移)
46
+
47
+ ### Bug 2:选项截断+description 显示混乱
48
+
49
+ **问题位置**:`extensions/grill-me-agent.ts` 第 602-618 行
50
+
51
+ ```typescript
52
+ const MAX_OPTION_LABEL = 50;
53
+ const truncated = truncateToWidth(label, MAX_OPTION_LABEL, "...");
54
+ return {
55
+ value: `opt-${i}`,
56
+ label: truncated,
57
+ description: truncated !== label ? opt : undefined,
58
+ };
59
+ ```
60
+
61
+ SelectList 的渲染流程:
62
+ 1. `renderItem` 对 `label` 使用 `truncateToWidth` 到主列宽度(截断为 50 字符 + "...")
63
+ 2. 如果 `description` 存在,使用两列布局,description 列也被 `truncateToWidth` 到剩余宽度
64
+
65
+ 当终端宽度不足以显示完整的 description 文本时,description 列也会被截断且**不换行**,产生用户看到的"左边截断 + 右边灰色不换行"效果。
66
+
67
+ ## 修复方案
68
+
69
+ ### 方案(根据用户建议)
70
+
71
+ **核心思路**:统一使用 `Ctrl+Shift+←` 作为返回键,恢复裸 `←` 的光标左移功能。
72
+
73
+ **详细改动**:
74
+
75
+ | 位置 | 当前行为(`dc0d3fa9`) | 修复后行为 |
76
+ |------|----------------------|-----------|
77
+ | `grill-me-agent.ts` handleInput | 裸 `←` 拦截为返回 | `Ctrl+Shift+←` 拦截为返回 |
78
+ | `ui-helpers.ts` uiInput handleInput | 裸 `←` 拦截为返回(破坏光标左移) | 移除裸 `←` 拦截 |
79
+ | `ui-helpers.ts` uiInput handleInput | `Ctrl+Shift+←` 返回(保留) | 保留 `Ctrl+Shift+←` |
80
+ | 选项列表(SelectList) | 用户按 `←` → 返回上一题 | 用户按 `Ctrl+Shift+←` → 返回上一题 |
81
+ | 输入框(Input) | 用户按 `←` → 返回上一题 ❌ | 用户按 `←` → 光标左移 ✅ |
82
+ | 输入框(Input) | 用户按 `Ctrl+Shift+←` → 返回上一题 | 保留 |
83
+
84
+ **Bug 2 修复**:直接移除截断+description 方案,恢复完整 label 显示。
85
+
86
+ ## 文件清单
87
+
88
+ ### 修改文件
89
+
90
+ | 文件路径 | 改动描述 | 风险等级 |
91
+ |---------|---------|---------|
92
+ | `extensions/ui-helpers.ts` | `uiInput.handleInput` 中移除裸 `←` 拦截(第 263-266 行);更新 JSDoc 注释 | 低 |
93
+ | `extensions/grill-me-agent.ts` | 1. `showQuestionTUI.handleInput` 中 `Key.left` → `Key.ctrlShift("left")` 2. 更新 hint 文案 3. 移除选项截断+description 方案,恢复完整 label | 低 |
94
+
95
+ ### 无新增/删除文件
96
+
97
+ ## 实施步骤
98
+
99
+ ### 步骤 1:修复 `ui-helpers.ts` — 移除 uiInput 中的裸 ← 拦截
100
+
101
+ - **前置条件**:无
102
+ - **改动文件**:`extensions/ui-helpers.ts`
103
+ - **改动内容**:
104
+
105
+ **a) 删除裸 ← 拦截(约第 263-266 行)**
106
+
107
+ 删除以下代码块:
108
+
109
+ ```typescript
110
+ // 左方向键 → 返回(优先于 Input 的光标左移)
111
+ if (backable && matchesKey(data, Key.left)) {
112
+ done(BACK_MARKER);
113
+ return;
114
+ }
115
+ ```
116
+
117
+ 保留 `Ctrl+Shift+←` 和 `Ctrl+Shift+→` 拦截不受影响。
118
+
119
+ **b) 更新 JSDoc 注释(约第 220 行)**
120
+
121
+ 将:
122
+
123
+ ```
124
+ * When backable=true, supports ← for back, Ctrl+Shift+← for back, and Ctrl+Shift+→ for submit+next.
125
+ ```
126
+
127
+ 改为:
128
+
129
+ ```
130
+ * When backable=true, supports Ctrl+Shift+← for back and Ctrl+Shift+→ for submit+next.
131
+ ```
132
+
133
+ - **验证方式**:
134
+ - 运行 `/dev-feat` → 进入"核心功能描述"输入框 → 按 `←` → 确认光标左移
135
+ - 按 `Ctrl+Shift+←` → 确认返回上一题
136
+ - 按 `Ctrl+Shift+→` → 确认跳过并继续
137
+
138
+ ### 步骤 2:修复 `grill-me-agent.ts` — 选项列表 ← 改为 Ctrl+Shift+←
139
+
140
+ - **前置条件**:步骤 1 完成
141
+ - **改动文件**:`extensions/grill-me-agent.ts`
142
+ - **改动内容**:
143
+
144
+ **a) 修改 handleInput 中的左方向键拦截(约第 671-674 行)**
145
+
146
+ 当前:
147
+
148
+ ```typescript
149
+ // 左方向键 → 返回上一题(SelectList 不处理 left 键,需自行拦截)
150
+ if (backable && currentIndex > 1 && matchesKey(data, Key.left)) {
151
+ done("__BACK__");
152
+ return;
153
+ }
154
+ ```
155
+
156
+ 改为:
157
+
158
+ ```typescript
159
+ // Ctrl+Shift+← → 返回上一题(SelectList 不处理该键,需自行拦截)
160
+ if (backable && currentIndex > 1 && matchesKey(data, Key.ctrlShift("left"))) {
161
+ done("__BACK__");
162
+ return;
163
+ }
164
+ ```
165
+
166
+ **b) 修改 hint 提示文字(约第 660 行)**
167
+
168
+ 当前:
169
+
170
+ ```typescript
171
+ const hint = backable && currentIndex > 1
172
+ ? " ↑↓ 导航 • Enter 选择 • ← 返回上一题 • Esc 取消全部评审"
173
+ : " ↑↓ 导航 • Enter 选择 • Esc 取消全部评审";
174
+ ```
175
+
176
+ 改为:
177
+
178
+ ```typescript
179
+ const hint = backable && currentIndex > 1
180
+ ? " ↑↓ 导航 • Enter 选择 • Ctrl+Shift+← 返回上一题 • Esc 取消全部评审"
181
+ : " ↑↓ 导航 • Enter 选择 • Esc 取消全部评审";
182
+ ```
183
+
184
+ - **验证方式**:
185
+ - 运行 `/dev-fix` → 进入 grill 评审
186
+ - 在第二个问题按 `←` → 确认**无反应**(正确,SelectList 不处理 left 键)
187
+ - 按 `Ctrl+Shift+←` → 确认返回到第一个问题
188
+ - 确认第一个问题选择的答案被保留
189
+
190
+ ### 步骤 3:修复 `grill-me-agent.ts` — 移除选项截断+description 方案
191
+
192
+ - **前置条件**:步骤 2 完成
193
+ - **改动文件**:`extensions/grill-me-agent.ts`
194
+ - **改动内容**:
195
+
196
+ **a) 修改选项构建逻辑(约第 602-618 行)**
197
+
198
+ 当前代码(`dc0d3fa9` 引入):
199
+
200
+ ```typescript
201
+ const MAX_OPTION_LABEL = 50;
202
+ const selectItems: SelectItem[] = q.options.map((opt, i) => {
203
+ const prefix = `(${String.fromCharCode(97 + i)}) `;
204
+ const label = opt === previousAnswer
205
+ ? `${prefix}${opt} - 上次选择`
206
+ : `${prefix}${opt}`;
207
+ const truncated = truncateToWidth(label, MAX_OPTION_LABEL, "...");
208
+ return {
209
+ value: `opt-${i}`,
210
+ label: truncated,
211
+ description: truncated !== label ? opt : undefined,
212
+ };
213
+ });
214
+ ```
215
+
216
+ 恢复为(还原到 `dc0d3fa9` 之前的状态):
217
+
218
+ ```typescript
219
+ const selectItems: SelectItem[] = q.options.map((opt, i) => ({
220
+ value: `opt-${i}`,
221
+ label: opt === previousAnswer
222
+ ? `(${String.fromCharCode(97 + i)}) ${opt} - 上次选择`
223
+ : `(${String.fromCharCode(97 + i)}) ${opt}`,
224
+ }));
225
+ ```
226
+
227
+ **b) 清理不再需要的 import(文件顶部约第 21-27 行)**
228
+
229
+ 如果 `truncateToWidth` 在文件其他地方没有使用(经 grep 检查仅在步骤 3a 处使用),则删除 import 中的 `truncateToWidth`:
230
+
231
+ ```typescript
232
+ // 删除: truncateToWidth,
233
+ import {
234
+ Container,
235
+ SelectList,
236
+ Text,
237
+ Spacer,
238
+ matchesKey,
239
+ Key,
240
+ type SelectItem,
241
+ } from "@earendil-works/pi-tui";
242
+ ```
243
+
244
+ - **验证方式**:
245
+ - 查看 grill 问题的选项 → 确认显示完整标签文本(而不是截断+description 两列显示)
246
+ - 当选项文本超宽时,SelectList 会自动截断到可用宽度(这是 SelectList 的内部行为,不需要手动干预)
247
+
248
+ ## 依赖关系
249
+
250
+ - 步骤 1 和步骤 2、3 无直接依赖,但建议按顺序 1→2→3 执行,以保证逻辑一致性
251
+ - 步骤 3 中需先确认 `truncateToWidth` 是否在其他地方使用,再决定是否清理 import
252
+
253
+ ## 测试策略
254
+
255
+ 手动测试清单:
256
+
257
+ | 测试场景 | 操作 | 预期结果 |
258
+ |---------|------|---------|
259
+ | 输入框左方向键 | 在 dev-* 输入框中按 `←` | 光标左移 ✅(修复点) |
260
+ | 输入框 Ctrl+Shift+← | 在 dev-* 输入框中按 `Ctrl+Shift+←` | 返回上一题 ✅ |
261
+ | 输入框 Ctrl+Shift+→ | 在 dev-* 输入框中按 `Ctrl+Shift+→` | 跳过并继续 ✅ |
262
+ | Grill 选项列表 ← | 在 grill 第二题按 `←` | 无反应(SelectList 不处理 left 键)✅ |
263
+ | Grill 选项列表 Ctrl+Shift+← | 在 grill 第二题按 `Ctrl+Shift+←` | 返回上一题 ✅(修复点) |
264
+ | Grill 自定义输入 ← | 进入自定义输入框按 `←` | 光标左移 ✅(修复点) |
265
+ | Grill 自定义输入 Ctrl+Shift+← | 在自定义输入框按 `Ctrl+Shift+←` | 返回选项列表 ✅ |
266
+ | Grill 选项完整显示 | 查看长选项文本 | 完整 label 显示 ✅(修复点) |
267
+ | 原有功能 | ↑↓ Enter Esc | 正常导航/选择/取消 ✅ |
268
+
269
+ ## 注意事项
270
+
271
+ - **最小改动**:仅修改 2 个文件中涉及的 4 个代码位置,总计约 10-15 行
272
+ - **不修改 pi-tui 库**:所有改动在 extensions/ 目录内完成
273
+ - **兼容性**:原有的 `Ctrl+Shift+←` 和 `Ctrl+Shift+→` 功能完全保留,新增的选项列表 `Ctrl+Shift+←` 与输入框保持一致的按键语义
274
+ - **SelectList 的 `← 返回上一题` 选项**:注意 `showQuestionTUI` 中还有一个 `"← 返回上一题"` 的 `SelectItem`(第 637 行),这是通过选择选项来返回,而非通过按键。此功能应保留,因为用户也可以通过 ↑↓ 导航到该选项后按 Enter 返回。但需要确认它与 `Key.left` 拦截的分离——按键拦截在 handleInput 中,选项选择在 SelectList 的 onSelect 回调中,两者互不干扰。保留该选项作为额外导航方式。
@@ -0,0 +1,166 @@
1
+ # 代码审查报告 — Grill 左方向键冲突与选项显示修复
2
+
3
+ **审查日期**: 2026-05-22 10:50
4
+ **审查范围**: `dc0d3fa9` 提交引入的两个回归问题的修复方案实现
5
+ **审查文件**: `extensions/grill-me-agent.ts`, `extensions/ui-helpers.ts`
6
+
7
+ ---
8
+
9
+ ## 审查概述
10
+
11
+ 对 `dc0d3fa9` 提交引入的两个 bug 的修复实现进行审查。当前工作树已部分实施修复方案,但仍存在若干问题。
12
+
13
+ ---
14
+
15
+ ## 严重问题 (Critical)
16
+
17
+ ### CRITICAL-1: `extensions/ui-helpers.ts` 末尾截断,文件损坏
18
+
19
+ **文件**: `extensions/ui-helpers.ts:849-850`
20
+ **问题**: 文件末尾被截断,`renderTreeNode` 函数的 `lines.push(...)` 语句不完整:
21
+
22
+ ```typescript
23
+ function renderTreeNode(node: TreeNode, prefix: string, isLast: boolean, lines: string[]): void {
24
+ const connector = isLast ? "└── " : "├── ";
25
+ lines.push(`${prefix}${connector}
26
+ ```
27
+
28
+ 缺少闭合反引号、分号和函数体的花括号。这导致:
29
+ - `renderTreeNode` 函数不完整
30
+ - `renderChildren` 函数完全丢失
31
+ - `buildTree` 调用、`extractFileChanges` 函数的返回语句全部丢失
32
+ - `sendWorkflowResult` 和 `buildWidgetState` 函数完全丢失
33
+
34
+ **影响**: 任何使用 `updateWorkflowWidget`(以及间接使用 `sendWorkflowResult`)的代码在运行时可能因为文件末尾语法错误导致模块加载失败。这是**破坏性**的。
35
+
36
+ **根因**: 前一次修改(非本次 `dc0d3fa9` 相关修改)中文件被截断。这是**独立于本次修复任务**的预存问题,但严重程度高,需要立即修复。
37
+
38
+ **建议修复**: 从 Git 恢复完整版本(`git checkout dc0d3fa9^ -- extensions/ui-helpers.ts`)然后将本次需要的更改应用回去。
39
+
40
+ ---
41
+
42
+ ## 中等问题 (Medium)
43
+
44
+ ### MEDIUM-1: 初始值未触发预览更新
45
+
46
+ **文件**: `extensions/ui-helpers.ts:237-239`
47
+ **问题**: 当 `uiInput` 接收 `initialValue` 时,`input.setValue(initialValue)` 被调用,但预览区域 (`previewText`) 未被更新。用户看到输入框中有初始值,但上方的预览区域为空,直到用户输入第一个字符后预览区域才更新。
48
+
49
+ ```typescript
50
+ const input = new Input(placeholder ?? "", width - 2);
51
+ if (initialValue) {
52
+ input.setValue(initialValue); // ← previewText 未同步更新
53
+ }
54
+ ```
55
+
56
+ **影响**: 当 grill 自定义输入有初始值(`previousAnswer`)时,预览区域初始为空白,造成视觉不一致。
57
+
58
+ **建议修复**: 在 `setValue` 调用后立即同步更新 `previewText`:
59
+
60
+ ```typescript
61
+ if (initialValue) {
62
+ input.setValue(initialValue);
63
+ // 同步更新预览
64
+ const wrapped = wrapTextWithAnsi(initialValue, width - 4);
65
+ const previewContent = wrapped
66
+ .map(l => theme.fg("dim", ` ${l}`))
67
+ .join("\n");
68
+ previewText.setText(previewContent);
69
+ }
70
+ ```
71
+
72
+ ### MEDIUM-2: 实时预览的宽度计算可能不准确
73
+
74
+ **文件**: `extensions/ui-helpers.ts:281`
75
+ **问题**: 预览区域的 `wrapTextWithAnsi` 调用使用的宽度为 `width - 4`,但实际预览文本是渲染在 ` ${l}`(2 个空格缩进)下方的,且预览 Text 组件本身不会被容器约束宽度。这意味着当终端宽度变化时,预览可能不会正确地根据可用宽度换行。
76
+
77
+ ```typescript
78
+ const wrapped = wrapTextWithAnsi(val, width - 4);
79
+ ```
80
+
81
+ 实际上 `width` 已经是 `process.stdout.columns - 6`(第 223 行),减去 6 后再减 4,实际有效宽度为 `termWidth - 10`,而容器中预览区域的实际可用宽度是 `width`(`termWidth - 6`)左右(考虑边框)。宽度计算存在偏差,可能导致预览内容过早换行。
82
+
83
+ **影响**: 预览显示可能与实际输入框中的换行效果不一致,但影响较小(预览仅用于视觉辅助)。
84
+
85
+ **建议修复**: 更新宽度为 `width - 2`(与输入框宽度一致):
86
+
87
+ ```typescript
88
+ const wrapped = wrapTextWithAnsi(val, width - 2); // 与 input 宽度一致
89
+ ```
90
+
91
+ ### MEDIUM-3: `description` 属性从 `__custom__` 选项中被移除
92
+
93
+ **文件**: `extensions/grill-me-agent.ts:616`
94
+ **问题**: `dc0d3fa9` 提交为自定义输入选项添加了 `description: "输入你自己的回答,不受选项限制"`,这在修复时被移除了。虽然这不影响功能,但该 description 提供了重要的用户引导信息,说明自定义输入选项的用途。
95
+
96
+ ```typescript
97
+ // dc0d3fa9 版本:
98
+ { value: "__custom__", label: customLabel, description: "输入你自己的回答,不受选项限制" }
99
+
100
+ // 当前版本:
101
+ { value: "__custom__", label: customLabel } // description 被移除
102
+ ```
103
+
104
+ **影响**: 用户可能在选择列表中看不到自定义输入选项的说明,降低可用性(尤其是首次使用 grill 的用户)。
105
+
106
+ **建议修复**: 恢复 description:
107
+
108
+ ```typescript
109
+ selectItems.push({
110
+ value: "__custom__",
111
+ label: customLabel,
112
+ description: "输入你自己的回答,不受选项限制",
113
+ });
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 低优先级问题 (Low)
119
+
120
+ ### LOW-1: `truncateToWidth` import 清理不完整
121
+
122
+ **文件**: `extensions/grill-me-agent.ts`
123
+ **问题**: `truncateToWidth` 已从 `@earendil-works/pi-tui` 的 import 中移除。但实现计划中提到的检查对 `truncateToWidth` 在文件其他位置的使用情况已确认不存在交叉引用——清理操作正确。无问题。
124
+
125
+ ✅ 此项已正确处理。
126
+
127
+ ### LOW-2: JSDoc 注释已更新但可进一步改进
128
+
129
+ **文件**: `extensions/ui-helpers.ts:210`
130
+ **问题**: JSDoc 注释已正确更新为:
131
+
132
+ ```typescript
133
+ * When backable=true, supports Ctrl+Shift+← for back and Ctrl+Shift+→ for submit+next.
134
+ ```
135
+
136
+ ✅ 已正确修复。
137
+
138
+ ### LOW-3: 提示文案同步正确
139
+
140
+ **文件**: `extensions/grill-me-agent.ts:649-652`
141
+ **问题**: hint 文案已从 `← 返回上一题` 更新为 `Ctrl+Shift+← 返回上一题`。
142
+
143
+ ✅ 已正确修复。
144
+
145
+ ---
146
+
147
+ ## 修复验证矩阵
148
+
149
+ | 测试场景 | 预期 | 当前状态 | 结论 |
150
+ |---------|------|---------|------|
151
+ | 输入框按 `←` | 光标左移 | `ui-helpers.ts` 中裸 `←` 拦截已移除 | ✅ |
152
+ | 输入框按 `Ctrl+Shift+←` | 返回上一题 | `Key.ctrlShift("left")` 拦截保留 | ✅ |
153
+ | 输入框按 `Ctrl+Shift+→` | 提交并继续 | 保留 | ✅ |
154
+ | Grill 选项列表按 `←` | 无反应(光标/导航不处理) | `grill-me-agent.ts` 已改为 `Key.ctrlShift("left")` | ✅ |
155
+ | Grill 选项列表按 `Ctrl+Shift+←` | 返回上一题 | 正确拦截 | ✅ |
156
+ | Grill 自定义输入按 `←` | 光标左移 | 依赖 `uiInput` 的裸 `←` 移除 | ✅ |
157
+ | 选项显示 | 完整 label,无截断+description | 截断逻辑已移除 | ✅ |
158
+ | 初始值预览 | 显示初始值预览 | **缺失** ❌ | MEDIUM-1 |
159
+
160
+ ---
161
+
162
+ ## 总结
163
+
164
+ **主要问题**: `extensions/ui-helpers.ts` 文件末端被截断损坏(CRITICAL-1),这是需要立即解决的独立于本次任务的预存问题。其余与 `dc0d3fa9` 修复直接相关的问题均为 medium 级别,修复方案本身是正确的。
165
+
166
+ 修复方案的三项核心改动(移除 uiInput 中的裸 ← 拦截、将 grill 选项列表的 ← 改为 Ctrl+Shift+←、移除选项截断+description)均已正确实现。`truncateToWidth` 的 import 清理也正确完成(该函数仍在 `boxify` 中使用)。
@@ -0,0 +1,473 @@
1
+ {
2
+ "version": 2,
3
+ "createdAt": "2026-05-21T18:41:14.437Z",
4
+ "updatedAt": "2026-05-21T18:41:14.437Z",
5
+ "prompt": "[fix] 修复 当前目录 中的 问题: 1.gitcommit哈希dc0d3fa9d2362f4fb5026378e472510902dacd8e的修改,增加的grill换行修复,现在是(a)左边简短被阶段黑色字体提问 右边灰色字体提问内容,没截断,但也没换行 2.grill拦截左方向键返回上一题功能正常,但是他破坏了那种grill提问自定义输入式的和dev-*输入式的左方向键,输入式的左方向键应该是光标向左,而现在是返回上一题了,原来的ctrl+shift+左是正常的且考虑了避开输入式的左方向键应该是光标向左的冲突\n\n**背景**:\n- 输入:见代码上下文\n- 预期行为:修复: 1.grll提问选项正确实现换行。 2.输入式的ui不能监听向左键,我的建议是grill提问环节的向左返回,统一改成和输入式一样的`ctrl+shift+左`来返回。\n- 当前错误:请描述当前错误\n**任务**:\n1. 不要仅仅消除报错(Suppress),要解决根本原因。\n2. 先读取相关代码和日志,诊断根因(多步推理,不要先给结论)。\n3. 提供至少一种修复方案,并说明为什么这样做。\n4. 编写测试用例复现该 Bug 并确认修复有效。\n**输出**:提供 diff 和两句话的根因分析。\n**约束**:只修 bug,不做重构;最小化改动;不要假设错误是微不足道的。",
6
+ "mode": "full-auto",
7
+ "steps": [
8
+ {
9
+ "status": "done",
10
+ "durationMs": 119262
11
+ },
12
+ {
13
+ "status": "done",
14
+ "loopCount": 1,
15
+ "durationMs": 244192
16
+ },
17
+ {
18
+ "status": "done",
19
+ "durationMs": 257239
20
+ }
21
+ ],
22
+ "currentStepIndex": 2,
23
+ "loopCounts": {
24
+ "worker-reviewer": 1
25
+ },
26
+ "planFilePath": ".pi-dev-output/pi-plans/20260522-113000-grill-left-arrow-fix.md",
27
+ "taskSummary": "fix - 修复 当前目录 中的 问题: 1.gitcommit哈希dc0d3fa9d2362f4fb5026378e472510902dacd8e的修改,增加的grill换行修复,现在是(a)左边简短被阶段黑色字体提问 右边灰色字体提问内容,没截断,但也没换行 2.grill拦截左方向键返回上一题功能正常,但是他破坏了那种grill提问自定义输入式的和dev-*输入式的左方向键,输入式的左方向键应该是光标向左,而现在是返回上一题了,原来的ctrl+shift+左是正常的且考虑了避开输入式的左方向键应该是光标向左的冲突",
28
+ "workflowType": "快速链式",
29
+ "fileChanges": [
30
+ {
31
+ "agent": "planner",
32
+ "stepIndex": 0,
33
+ "type": "edit",
34
+ "filePath": "ui-helpers.ts",
35
+ "timestamp": "2026-05-21T18:32:52.982Z"
36
+ },
37
+ {
38
+ "agent": "planner",
39
+ "stepIndex": 0,
40
+ "type": "edit",
41
+ "filePath": "grill-me-agent.ts",
42
+ "timestamp": "2026-05-21T18:32:52.982Z"
43
+ },
44
+ {
45
+ "agent": "planner",
46
+ "stepIndex": 0,
47
+ "type": "edit",
48
+ "filePath": "tui.editor.cursorLeft",
49
+ "timestamp": "2026-05-21T18:32:52.982Z"
50
+ },
51
+ {
52
+ "agent": "planner",
53
+ "stepIndex": 0,
54
+ "type": "edit",
55
+ "filePath": "extensions/grill-me-agent.ts",
56
+ "timestamp": "2026-05-21T18:32:52.982Z"
57
+ },
58
+ {
59
+ "agent": "planner",
60
+ "stepIndex": 0,
61
+ "type": "edit",
62
+ "filePath": "showQuestionTUI.handleInput",
63
+ "timestamp": "2026-05-21T18:32:52.982Z"
64
+ },
65
+ {
66
+ "agent": "planner",
67
+ "stepIndex": 0,
68
+ "type": "edit",
69
+ "filePath": "extensions/ui-helpers.ts",
70
+ "timestamp": "2026-05-21T18:32:52.982Z"
71
+ },
72
+ {
73
+ "agent": "planner",
74
+ "stepIndex": 0,
75
+ "type": "edit",
76
+ "filePath": "uiInput.handleInput",
77
+ "timestamp": "2026-05-21T18:32:52.982Z"
78
+ },
79
+ {
80
+ "agent": "planner",
81
+ "stepIndex": 0,
82
+ "type": "edit",
83
+ "filePath": "tui.editor.cursorWordLeft",
84
+ "timestamp": "2026-05-21T18:32:52.983Z"
85
+ },
86
+ {
87
+ "agent": "planner",
88
+ "stepIndex": 0,
89
+ "type": "edit",
90
+ "filePath": "!this.text",
91
+ "timestamp": "2026-05-21T18:32:52.983Z"
92
+ },
93
+ {
94
+ "agent": "planner",
95
+ "stepIndex": 0,
96
+ "type": "new",
97
+ "filePath": ".pi-dev-output/pi-plans/20260522-113000-grill-left-arrow-fix.md",
98
+ "timestamp": "2026-05-21T18:32:53.003Z"
99
+ },
100
+ {
101
+ "agent": "planner",
102
+ "stepIndex": 0,
103
+ "type": "new",
104
+ "filePath": ".pi-dev-output/pi-workflow/checkpoint.json",
105
+ "timestamp": "2026-05-21T18:32:53.004Z"
106
+ },
107
+ {
108
+ "agent": "worker",
109
+ "stepIndex": 1,
110
+ "type": "edit",
111
+ "filePath": "src/main.rs\\\\\\\",",
112
+ "timestamp": "2026-05-21T18:33:23.077Z"
113
+ },
114
+ {
115
+ "agent": "worker",
116
+ "stepIndex": 1,
117
+ "type": "new",
118
+ "filePath": "代码xxx.ts\\n",
119
+ "timestamp": "2026-05-21T18:33:31.217Z"
120
+ },
121
+ {
122
+ "agent": "worker",
123
+ "stepIndex": 1,
124
+ "type": "edit",
125
+ "filePath": "代码xxx.rs\\n",
126
+ "timestamp": "2026-05-21T18:33:31.231Z"
127
+ },
128
+ {
129
+ "agent": "worker",
130
+ "stepIndex": 1,
131
+ "type": "edit",
132
+ "filePath": "config.json\\\")",
133
+ "timestamp": "2026-05-21T18:33:33.257Z"
134
+ },
135
+ {
136
+ "agent": "worker",
137
+ "stepIndex": 1,
138
+ "type": "edit",
139
+ "filePath": "src/main.rs\\\",",
140
+ "timestamp": "2026-05-21T18:33:33.261Z"
141
+ },
142
+ {
143
+ "agent": "worker",
144
+ "stepIndex": 1,
145
+ "type": "new",
146
+ "filePath": "/delete:",
147
+ "timestamp": "2026-05-21T18:33:38.655Z"
148
+ },
149
+ {
150
+ "agent": "worker",
151
+ "stepIndex": 1,
152
+ "type": "delete",
153
+ "filePath": "\\\\\\\\s*(.+)/i);\\\\n",
154
+ "timestamp": "2026-05-21T18:33:40.847Z"
155
+ },
156
+ {
157
+ "agent": "worker",
158
+ "stepIndex": 1,
159
+ "type": "new",
160
+ "filePath": "\\\\\\\\s*(.+)/i);\\\\n",
161
+ "timestamp": "2026-05-21T18:33:40.898Z"
162
+ },
163
+ {
164
+ "agent": "worker",
165
+ "stepIndex": 1,
166
+ "type": "edit",
167
+ "filePath": "path\\\\\\\",",
168
+ "timestamp": "2026-05-21T18:33:40.925Z"
169
+ },
170
+ {
171
+ "agent": "worker",
172
+ "stepIndex": 1,
173
+ "type": "new",
174
+ "filePath": "path\\\\\\\",",
175
+ "timestamp": "2026-05-21T18:33:41.017Z"
176
+ },
177
+ {
178
+ "agent": "worker",
179
+ "stepIndex": 1,
180
+ "type": "edit",
181
+ "filePath": "/new:/delete:",
182
+ "timestamp": "2026-05-21T18:33:41.233Z"
183
+ },
184
+ {
185
+ "agent": "worker",
186
+ "stepIndex": 1,
187
+ "type": "edit",
188
+ "filePath": "\\\\\\\\s*(.+)/i);\\\\n",
189
+ "timestamp": "2026-05-21T18:33:41.685Z"
190
+ },
191
+ {
192
+ "agent": "worker",
193
+ "stepIndex": 1,
194
+ "type": "delete",
195
+ "filePath": "path\\\\\\\"\\\\n",
196
+ "timestamp": "2026-05-21T18:33:41.711Z"
197
+ },
198
+ {
199
+ "agent": "worker",
200
+ "stepIndex": 1,
201
+ "type": "delete",
202
+ "filePath": "path\\\\\\\")\\\\n",
203
+ "timestamp": "2026-05-21T18:33:41.794Z"
204
+ },
205
+ {
206
+ "agent": "worker",
207
+ "stepIndex": 1,
208
+ "type": "new",
209
+ "filePath": "代码xxx.ts\\\\n",
210
+ "timestamp": "2026-05-21T18:33:42.360Z"
211
+ },
212
+ {
213
+ "agent": "worker",
214
+ "stepIndex": 1,
215
+ "type": "edit",
216
+ "filePath": "/new:/delet",
217
+ "timestamp": "2026-05-21T18:33:42.609Z"
218
+ },
219
+ {
220
+ "agent": "worker",
221
+ "stepIndex": 1,
222
+ "type": "edit",
223
+ "filePath": "/new:/delete",
224
+ "timestamp": "2026-05-21T18:33:42.611Z"
225
+ },
226
+ {
227
+ "agent": "worker",
228
+ "stepIndex": 1,
229
+ "type": "edit",
230
+ "filePath": "/new:/del",
231
+ "timestamp": "2026-05-21T18:33:42.613Z"
232
+ },
233
+ {
234
+ "agent": "worker",
235
+ "stepIndex": 1,
236
+ "type": "edit",
237
+ "filePath": "extensions/ui-helpers.ts",
238
+ "timestamp": "2026-05-21T18:34:54.850Z"
239
+ },
240
+ {
241
+ "agent": "worker",
242
+ "stepIndex": 1,
243
+ "type": "edit",
244
+ "filePath": "uiInput.handleInput",
245
+ "timestamp": "2026-05-21T18:34:54.850Z"
246
+ },
247
+ {
248
+ "agent": "worker",
249
+ "stepIndex": 1,
250
+ "type": "edit",
251
+ "filePath": "tui.editor.cursorLeft",
252
+ "timestamp": "2026-05-21T18:34:54.850Z"
253
+ },
254
+ {
255
+ "agent": "worker",
256
+ "stepIndex": 1,
257
+ "type": "edit",
258
+ "filePath": "extensions/grill-me-agent.ts",
259
+ "timestamp": "2026-05-21T18:34:54.850Z"
260
+ },
261
+ {
262
+ "agent": "worker",
263
+ "stepIndex": 1,
264
+ "type": "edit",
265
+ "filePath": "showQuestionTUI.handleInput",
266
+ "timestamp": "2026-05-21T18:34:54.850Z"
267
+ },
268
+ {
269
+ "agent": "worker",
270
+ "stepIndex": 1,
271
+ "type": "edit",
272
+ "filePath": "grill-me-agent.ts",
273
+ "timestamp": "2026-05-21T18:34:54.850Z"
274
+ },
275
+ {
276
+ "agent": "worker",
277
+ "stepIndex": 1,
278
+ "type": "edit",
279
+ "filePath": "ui-helpers.ts",
280
+ "timestamp": "2026-05-21T18:34:54.850Z"
281
+ },
282
+ {
283
+ "agent": "worker",
284
+ "stepIndex": 1,
285
+ "type": "edit",
286
+ "filePath": "Key.left",
287
+ "timestamp": "2026-05-21T18:34:54.850Z"
288
+ },
289
+ {
290
+ "agent": "reviewer",
291
+ "stepIndex": 1,
292
+ "type": "edit",
293
+ "filePath": "extensions/ui-helpers.ts",
294
+ "timestamp": "2026-05-21T18:36:57.169Z"
295
+ },
296
+ {
297
+ "agent": "reviewer",
298
+ "stepIndex": 1,
299
+ "type": "edit",
300
+ "filePath": "uiInput.handleInput",
301
+ "timestamp": "2026-05-21T18:36:57.169Z"
302
+ },
303
+ {
304
+ "agent": "reviewer",
305
+ "stepIndex": 1,
306
+ "type": "edit",
307
+ "filePath": "tui.editor.cursorLeft",
308
+ "timestamp": "2026-05-21T18:36:57.169Z"
309
+ },
310
+ {
311
+ "agent": "reviewer",
312
+ "stepIndex": 1,
313
+ "type": "edit",
314
+ "filePath": "extensions/grill-me-agent.ts",
315
+ "timestamp": "2026-05-21T18:36:57.169Z"
316
+ },
317
+ {
318
+ "agent": "reviewer",
319
+ "stepIndex": 1,
320
+ "type": "edit",
321
+ "filePath": "showQuestionTUI.handleInput",
322
+ "timestamp": "2026-05-21T18:36:57.169Z"
323
+ },
324
+ {
325
+ "agent": "reviewer",
326
+ "stepIndex": 1,
327
+ "type": "edit",
328
+ "filePath": "grill-me-agent.ts",
329
+ "timestamp": "2026-05-21T18:36:57.169Z"
330
+ },
331
+ {
332
+ "agent": "reviewer",
333
+ "stepIndex": 1,
334
+ "type": "edit",
335
+ "filePath": "ui-helpers.ts",
336
+ "timestamp": "2026-05-21T18:36:57.169Z"
337
+ },
338
+ {
339
+ "agent": "reviewer",
340
+ "stepIndex": 1,
341
+ "type": "edit",
342
+ "filePath": "Key.left",
343
+ "timestamp": "2026-05-21T18:36:57.169Z"
344
+ },
345
+ {
346
+ "agent": "reviewer",
347
+ "stepIndex": 1,
348
+ "type": "new",
349
+ "filePath": ".pi-dev-output/pi-review/md/review-20260522-105000.md",
350
+ "timestamp": "2026-05-21T18:36:57.193Z"
351
+ },
352
+ {
353
+ "agent": "docWriter",
354
+ "stepIndex": 2,
355
+ "type": "edit",
356
+ "filePath": "extensions/ui-helpers.ts",
357
+ "timestamp": "2026-05-21T18:41:14.410Z"
358
+ },
359
+ {
360
+ "agent": "docWriter",
361
+ "stepIndex": 2,
362
+ "type": "edit",
363
+ "filePath": "uiInput.handleInput",
364
+ "timestamp": "2026-05-21T18:41:14.410Z"
365
+ },
366
+ {
367
+ "agent": "docWriter",
368
+ "stepIndex": 2,
369
+ "type": "edit",
370
+ "filePath": "tui.editor.cursorLeft",
371
+ "timestamp": "2026-05-21T18:41:14.410Z"
372
+ },
373
+ {
374
+ "agent": "docWriter",
375
+ "stepIndex": 2,
376
+ "type": "edit",
377
+ "filePath": "extensions/grill-me-agent.ts",
378
+ "timestamp": "2026-05-21T18:41:14.410Z"
379
+ },
380
+ {
381
+ "agent": "docWriter",
382
+ "stepIndex": 2,
383
+ "type": "edit",
384
+ "filePath": "showQuestionTUI.handleInput",
385
+ "timestamp": "2026-05-21T18:41:14.410Z"
386
+ },
387
+ {
388
+ "agent": "docWriter",
389
+ "stepIndex": 2,
390
+ "type": "edit",
391
+ "filePath": "grill-me-agent.ts",
392
+ "timestamp": "2026-05-21T18:41:14.410Z"
393
+ },
394
+ {
395
+ "agent": "docWriter",
396
+ "stepIndex": 2,
397
+ "type": "edit",
398
+ "filePath": "ui-helpers.ts",
399
+ "timestamp": "2026-05-21T18:41:14.410Z"
400
+ },
401
+ {
402
+ "agent": "docWriter",
403
+ "stepIndex": 2,
404
+ "type": "edit",
405
+ "filePath": "Key.left",
406
+ "timestamp": "2026-05-21T18:41:14.410Z"
407
+ },
408
+ {
409
+ "agent": "docWriter",
410
+ "stepIndex": 2,
411
+ "type": "edit",
412
+ "filePath": "tui.editor.cursorWordLeft",
413
+ "timestamp": "2026-05-21T18:41:14.411Z"
414
+ },
415
+ {
416
+ "agent": "docWriter",
417
+ "stepIndex": 2,
418
+ "type": "edit",
419
+ "filePath": "!this.text",
420
+ "timestamp": "2026-05-21T18:41:14.411Z"
421
+ },
422
+ {
423
+ "agent": "docWriter",
424
+ "stepIndex": 2,
425
+ "type": "edit",
426
+ "filePath": "README.md",
427
+ "timestamp": "2026-05-21T18:41:14.431Z"
428
+ }
429
+ ],
430
+ "subAgentRuns": 4,
431
+ "filesModified": 46,
432
+ "filesCreated": 8,
433
+ "agentRunHistory": [
434
+ {
435
+ "agent": "planner",
436
+ "stepIndex": 0,
437
+ "startedAt": "2026-05-21T18:30:53.742Z",
438
+ "durationMs": 119236,
439
+ "exitCode": 0,
440
+ "toolCount": 0
441
+ },
442
+ {
443
+ "agent": "worker",
444
+ "stepIndex": 1,
445
+ "startedAt": "2026-05-21T18:32:53.005Z",
446
+ "durationMs": 121842,
447
+ "exitCode": 0,
448
+ "toolCount": 192
449
+ },
450
+ {
451
+ "agent": "reviewer",
452
+ "stepIndex": 1,
453
+ "startedAt": "2026-05-21T18:34:54.873Z",
454
+ "durationMs": 122293,
455
+ "exitCode": 0,
456
+ "toolCount": 200
457
+ },
458
+ {
459
+ "agent": "docWriter",
460
+ "stepIndex": 2,
461
+ "startedAt": "2026-05-21T18:36:57.198Z",
462
+ "durationMs": 257209,
463
+ "exitCode": 0,
464
+ "toolCount": 209
465
+ }
466
+ ],
467
+ "baseline": [
468
+ {
469
+ "path": ".pi-dev-output/pi-grill/answers/answer-mpfttme1-20260522-0230.md",
470
+ "hash": "4c3eeca3d8d77d1da40cf2ce9abd13e29fe21d70"
471
+ }
472
+ ]
473
+ }
@@ -0,0 +1,31 @@
1
+ # Release v0.4.2
2
+
3
+ ## 🚀 Features
4
+
5
+ - **Esc 双击确认停止机制** — 工作流运行期间,首次按 Esc 提示再次确认,3 秒内双击才终止工作流,避免误触中断
6
+ - **Grill 交互增强**
7
+ - 选项标签超长时自动截断,完整文本显示在 description 中
8
+ - 输入框上方增加实时换行预览区域
9
+ - 左方向键支持返回上一题
10
+ - 优化导航提示文字
11
+ - **独立超时配置** — reviewer 子代理新增 `reviewTimeoutMs` 独立超时,不再与 worker 共用超时时间(trimmer: 20min, worker: 30min, reviewer: 15min)
12
+ - **新主题** — 添加 `oh-my-pi-titanium` 主题
13
+ - **循环计数显示** — loop-group 在工作时正确显示 `第 N 次循环`,时序与执行同步
14
+
15
+ ## 🐛 Bug Fixes
16
+
17
+ - **Git diff 解析** — 改用简单字符串分割替代正则匹配,避免解析出 `checkpoint-${planId}.json`、`[],\n\t\t\t\toutputs:` 等垃圾条目
18
+ - **循环计数器时序** — 修复循环计数从 planner 进入 loop 时显示错误(0→1→1→2 的问题),现正确为 0→1→2→3
19
+ - **超时显示位置** — 超时时间从 loop 组迁移到具体的子代理步骤上,显示更准确
20
+ - **工作流状态重置** — 修复 sub-step 状态重置函数名错误
21
+
22
+ ## 🔧 Refactor
23
+
24
+ - UI 循环计数显示重构,子步骤时长/超时信息改进
25
+ - 文件变更检测支持 git 标准状态码(M/A/D)
26
+
27
+ ## 📦 Files Changed
28
+
29
+ ```
30
+ 41 files changed, 7351 insertions(+), 62 deletions(-)
31
+ ```
@@ -0,0 +1,42 @@
1
+ # Release v0.4.3
2
+
3
+ ## 🐛 Bug Fixes
4
+
5
+ ### Grill 选项完整文本不可见(选项不换行)
6
+ 选项列表中超长文本会被截断显示 `...`,但完整文本只能通过 `description` 列查看,而该列**单行显示且宽度受限**,导致长文本依旧被截断,用户无法阅读完整选项内容。
7
+
8
+ **修复方案**:移除 `description` 列,改为**选项预览面板**。
9
+
10
+ ```
11
+ 之前: (a) a[推荐] 最终用户... a[推荐] 最终用户(运维/... ← 灰色列单行截断
12
+ 之后: (a) a[推荐] 最终用户(运维/系统管理员)——需要快...
13
+ a[推荐] 最终用户(运维/系统管理员)——需要快速部署
14
+ rksh-server、通过客户端连接远程服务器、使用终端和 ← 预览面板自动换行
15
+ ```
16
+
17
+ - 选项列表下方新增 `Text` 预览面板,展示当前选中选项的完整文本
18
+ - 使用 `wrapTextWithAnsi` 实现自动换行,灰色 `dim` 样式
19
+ - 跟随 `↑↓` 导航实时更新,初始化时默认显示第一个选项
20
+ - 选中 `✏️ 自定义输入` 或 `← 返回上一题` 时预览自动清空
21
+
22
+ ### Grill 左方向键与输入框光标左移冲突
23
+ 选项列表中 `←` 键被拦截用于"返回上一题",导致自定义输入框中 `←` 键无法正常移动光标编辑文本。
24
+
25
+ **修复方案**:将返回上一题的快捷键从 `←` 改为 `Ctrl+Shift+←`,输入框中 `←`/`→` 恢复为光标移动。
26
+
27
+ | 场景 | 之前 | 之后 |
28
+ |------|------|------|
29
+ | 选项列表返回 | `←` | `Ctrl+Shift+←` |
30
+ | 输入框返回 | `←`(吃掉键,无法编辑) | `Ctrl+Shift+←` |
31
+ | 输入框光标 | ❌ 左移被拦截 | ✅ `←`/`→` 正常移动 |
32
+
33
+ ## 📦 Files Changed
34
+
35
+ ```
36
+ 2 files changed, 65 insertions(+), 15 deletions(-)
37
+ ```
38
+
39
+ ### Changed
40
+
41
+ - `extensions/grill-me-agent.ts` — 预览面板 + 动态截断宽度 + 键位修正
42
+ - `extensions/ui-helpers.ts` — 移除 `Key.left` 拦截,恢复输入框光标左移
package/README.md CHANGED
@@ -56,7 +56,8 @@ pi-package/
56
56
  │ ├── git-commands.ts # git-sub-agent 命令
57
57
  │ ├── grill-me-agent.ts # Grill + PRD 运行时:设计评审、PRD 生成
58
58
  │ ├── sub-agents.ts # 子代理系统:git-sub-agent + review-sub-agent
59
- └── workflow-engine.ts # 工作流编排引擎(由 dev-prompts.ts 引入)
59
+ ├── workflow-engine.ts # 工作流编排引擎(由 dev-prompts.ts 引入)
60
+ │ └── ui-helpers.ts # TUI 组件构建器(Select/Confirm/Input/Widget)
60
61
  └── themes/
61
62
  └── claude-code-theme.json # Claude Code CLI 风格主题
62
63
  ```
@@ -342,11 +343,22 @@ Grill("拷问式评审")是提交方案前由 AI sub-agent 从多个维度
342
343
  ### 交互形式
343
344
 
344
345
  每道问题在 TUI 中以 SelectList 呈现:
345
- - 选项列表:`(a) ...` `(b) ...` `(c) ...`
346
+ - 选项列表:`(a) ...` `(b) ...` `(c) ...`(完整显示,不截断)
346
347
  - 最末选项:`✏️ 自定义输入` — 可输入自己的回答
347
- - 导航:↑↓ 选择,Enter 确认,Esc 取消全部评审
348
+ - 导航:↑↓ 选择,Enter 确认
349
+ - 返回:`Ctrl+Shift+←` 返回上一题(输入框中也可用 `Ctrl+Shift+←` 返回上一步)
350
+ - 跳过:`Ctrl+Shift+→` 在输入框中跳过当前输入并继续
351
+ - 取消:Esc 取消全部评审
348
352
  - 进度:标题栏显示 `问题 3/18`
349
353
 
354
+ ### 输入框特性
355
+
356
+ 自定义输入和 `/dev-*` 向导中的输入框支持:
357
+ - **实时换行预览**:输入超长文本时,输入框上方会显示完整的换行预览(灰色文字),实时跟随输入变化
358
+ - **光标操作**:`←` 和 `→` 键可正常移动光标编辑已有内容(不触发返回)
359
+ - **返回上一题**:`Ctrl+Shift+←` 在输入框中返回上一题
360
+ - **跳过输入**:`Ctrl+Shift+→` 提交当前内容(可为空)并继续
361
+
350
362
  ## PRD 文档生成
351
363
 
352
364
  仅 `/dev-feat` 命令在执行完成后自动触发 PRD 生成。其余 `/dev-*` 命令不包含此阶段。
@@ -407,6 +419,12 @@ A: 在 `extensions/grill-me-agent.ts` 中修改对应 AgentDef 的 `systemPrompt
407
419
  **Q: 评审结果是否影响原提示词?**
408
420
  A: 评审问答以「设计评审记录」区块追加到原提示词末尾,原提示词内容不变。主代理执行时会同时参考原需求 + 评审中确认的决策。
409
421
 
422
+ **Q: Grill 中如何返回上一题?**
423
+ A: 使用 `Ctrl+Shift+←` 返回上一题(在选项列表和自定义输入框中均适用)。裸 `←` 键在选项列表中无效果,在输入框中用于光标左移编辑文本。
424
+
425
+ **Q: 自定义输入框中的键位有哪些?**
426
+ A: `Enter` 确认提交,`Esc` 取消返回选项列表,`Ctrl+Shift+←` 返回上一题,`Ctrl+Shift+→` 跳过输入并继续,方向键 `←`/`→` 用于移动光标编辑已有文本。
427
+
410
428
  **Q: `grill-with-docs` skill 和 `/dev-*` 内置的 Grill 有什么区别?**
411
429
  A: `grill-with-docs` 是可独立调用的 skill(`/skill:grill-with-docs`),侧重领域术语统一和文档同步(更新 CONTEXT.md、创建 ADR)。`/dev-*` 内置的 Grill 是任务向导的一部分,侧重方案评审,不涉及文档持久化。
412
430
 
@@ -24,6 +24,7 @@ import {
24
24
  matchesKey,
25
25
  Key,
26
26
  truncateToWidth,
27
+ wrapTextWithAnsi,
27
28
  type SelectItem,
28
29
  } from "@earendil-works/pi-tui";
29
30
  import { uiSelect, uiConfirm, uiInput } from "./ui-helpers";
@@ -592,7 +593,23 @@ export async function runGrillPhase(
592
593
  };
593
594
  }
594
595
 
595
- /** Show a single grill question as TUI SelectList + custom input option. */
596
+ /**
597
+ * Show a single grill question as TUI SelectList with option items and custom input entry.
598
+ *
599
+ * Navigation:
600
+ * - ↑↓ 选择, Enter 确认, Esc 取消全部评审
601
+ * - Ctrl+Shift+← 返回上一题(仅当 backable=true 且 currentIndex > 1 时生效)
602
+ * - 选择 "✏️ 自定义输入" 进入文本输入模式
603
+ *
604
+ * @param ctx - Extension command context for TUI rendering
605
+ * @param q - The grill question to display
606
+ * @param currentIndex - 1-based index of current question
607
+ * @param totalCount - Total number of questions
608
+ * @param titlePrefix - Prefix for the question title bar
609
+ * @param backable - Whether navigating back to previous question is allowed
610
+ * @param previousAnswer - Previous answer to pre-fill as "上次选择" marker
611
+ * @returns Selected option text, "__BACK__" for back navigation, or null for cancel
612
+ */
596
613
  async function showQuestionTUI(
597
614
  ctx: ExtensionCommandContext,
598
615
  q: GrillQuestion,
@@ -602,18 +619,19 @@ async function showQuestionTUI(
602
619
  backable = false,
603
620
  previousAnswer?: string,
604
621
  ): Promise<string | null> {
605
- const MAX_OPTION_LABEL = 50;
622
+ // 根据终端宽度计算截断宽度
623
+ const termWidth = process.stdout.columns || 120;
624
+ const maxOptWidth = Math.min(termWidth - 12, 100);
606
625
  const selectItems: SelectItem[] = q.options.map((opt, i) => {
607
626
  const prefix = `(${String.fromCharCode(97 + i)}) `;
608
627
  const label = opt === previousAnswer
609
628
  ? `${prefix}${opt} - 上次选择`
610
629
  : `${prefix}${opt}`;
611
- const truncated = truncateToWidth(label, MAX_OPTION_LABEL, "...");
630
+ const truncated = truncateToWidth(label, maxOptWidth, "...");
612
631
  return {
613
632
  value: `opt-${i}`,
614
633
  label: truncated,
615
- // 只有被截断时才提供 description,展示完整文本
616
- description: truncated !== label ? opt : undefined,
634
+ // 完整文本由下方的预览面板展示(支持换行),description 列无法换行故移除
617
635
  };
618
636
  });
619
637
 
@@ -650,14 +668,46 @@ async function showQuestionTUI(
650
668
  description: (s) => theme.fg("muted", s),
651
669
  scrollInfo: (s) => theme.fg("dim", s),
652
670
  noMatch: (s) => theme.fg("warning", s),
671
+ }, {
672
+ minPrimaryColumnWidth: 30,
673
+ maxPrimaryColumnWidth: maxOptWidth + 2,
674
+ truncatePrimary: ({ text, maxWidth }) => truncateToWidth(text, maxWidth, "..."),
653
675
  });
654
676
  selectList.onSelect = (item) => done(item.value);
655
677
  selectList.onCancel = () => done(null);
656
678
  container.addChild(selectList);
657
679
 
680
+ // 完整选项预览面板(支持换行,展示当前选中选项的完整文本)
681
+ const previewWidth = Math.max(30, termWidth - 8);
682
+ const previewText = new Text("", 0, 0);
683
+ container.addChild(new Spacer(1));
684
+ container.addChild(previewText);
685
+
686
+ // 初始化预览为第一个选项
687
+ if (q.options.length > 0) {
688
+ const initialWrapped = wrapTextWithAnsi(q.options[0], previewWidth);
689
+ previewText.setText(
690
+ initialWrapped.map(l => theme.fg("dim", ` ${l}`)).join("\n")
691
+ );
692
+ }
693
+
694
+ selectList.onSelectionChange = (item) => {
695
+ if (item.value.startsWith("opt-")) {
696
+ const idx = parseInt(item.value.replace("opt-", ""), 10);
697
+ const fullText = q.options[idx];
698
+ const wrapped = wrapTextWithAnsi(fullText, previewWidth);
699
+ previewText.setText(
700
+ wrapped.map(l => theme.fg("dim", ` ${l}`)).join("\n")
701
+ );
702
+ } else {
703
+ previewText.setText("");
704
+ }
705
+ tui.requestRender();
706
+ };
707
+
658
708
  container.addChild(new Spacer(1));
659
709
  const hint = backable && currentIndex > 1
660
- ? " ↑↓ 导航 • Enter 选择 • 返回上一题 • Esc 取消全部评审"
710
+ ? " ↑↓ 导航 • Enter 选择 • Ctrl+Shift+← 返回上一题 • Esc 取消全部评审"
661
711
  : " ↑↓ 导航 • Enter 选择 • Esc 取消全部评审";
662
712
  container.addChild(
663
713
  new Text(theme.fg("dim", hint), 0, 0),
@@ -668,8 +718,8 @@ async function showQuestionTUI(
668
718
  render: (w) => container.render(w),
669
719
  invalidate: () => container.invalidate(),
670
720
  handleInput: (data) => {
671
- // 左方向键 → 返回上一题(SelectList 不处理 left 键,需自行拦截)
672
- if (backable && currentIndex > 1 && matchesKey(data, Key.left)) {
721
+ // Ctrl+Shift+← → 返回上一题(SelectList 不处理该键,需自行拦截)
722
+ if (backable && currentIndex > 1 && matchesKey(data, Key.ctrlShift("left"))) {
673
723
  done("__BACK__");
674
724
  return;
675
725
  }
@@ -205,9 +205,14 @@ export function uiConfirm(
205
205
  // ── Input (replaces ctx.ui.input) ────────────────────────────
206
206
 
207
207
  /**
208
- * Show an input dialog with proper wrapping.
208
+ * Show an input dialog with proper wrapping and live preview.
209
+ *
210
+ * Features:
211
+ * - 实时换行预览:输入框上方显示完整的换行预览(跟随输入实时更新)
212
+ * - 方向键 ←/→ 可正常移动光标编辑已有内容
213
+ *
209
214
  * Returns the entered string, or BACK_MARKER on back, or undefined on cancel.
210
- * When backable=true, supports ← for back, Ctrl+Shift+← for back, and Ctrl+Shift+→ for submit+next.
215
+ * When backable=true, supports Ctrl+Shift+← for back and Ctrl+Shift+→ for submit+next.
211
216
  */
212
217
  export function uiInput(
213
218
  ctx: ExtensionCommandContext,
@@ -260,11 +265,6 @@ export function uiInput(
260
265
  render: (w) => container.render(w),
261
266
  invalidate: () => container.invalidate(),
262
267
  handleInput: (data) => {
263
- // 左方向键 → 返回(优先于 Input 的光标左移)
264
- if (backable && matchesKey(data, Key.left)) {
265
- done(BACK_MARKER);
266
- return;
267
- }
268
268
 
269
269
  // Intercept back/next keys before passing to Input
270
270
  if (backable) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghyper9023/pi-dev-workflow",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "keywords": [
5
5
  "pi-package"
6
6
  ],