pi-multi-viewers 0.8.0 → 0.8.1
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.
- package/AGENTS.md +1 -1
- package/docs/design.md +13 -9
- package/docs/reviews/2026-09-25-multi-viewers-postfix-review.md +211 -0
- package/docs/reviews/README.md +1 -0
- package/extensions/multi-viewers/index.ts +24 -15
- package/extensions/multi-viewers/shared.ts +51 -42
- package/mv_cli.py +2 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -233,7 +233,7 @@ loop、状态从 git 共享事实推导、单一事实源 = protocol.json、无
|
|
|
233
233
|
|
|
234
234
|
## 安装/发版状态(2026-09-11)
|
|
235
235
|
|
|
236
|
-
- **当前形态**:prompt × 1(multi-viewers-setup 建视角)+ extension ×
|
|
236
|
+
- **当前形态**:prompt × 1(multi-viewers-setup 建视角)+ extension × 1(一单元注册三命令)
|
|
237
237
|
——一个扩展单元内注册三命令(`multi-viewers` 分析 / `multi-viewers-finish` 收尾 /
|
|
238
238
|
`multi-viewers-say` 插话;前两个靠 CLI 机器标记行取值 `[prepare] spec=` /
|
|
239
239
|
`[start] dir=` / `[start] watch=` / `[status]`,后者零 LLM 直接 spawn human_sayer.py;
|
package/docs/design.md
CHANGED
|
@@ -567,19 +567,23 @@ commit 是溯源记录、本节是长期引用点——不并存两份权威值
|
|
|
567
567
|
内容起草仍是真 LLM 工作(任务类型/产出形态),故为**两段式**——骨架零 LLM,
|
|
568
568
|
起草/摘要走普通对话;门禁取消则保留 spec,用户编辑后自行 `mv.sh --start`。
|
|
569
569
|
**收尾状态语义(2026-09-25 评审)**:`running` 等待;`done | stalled` 并流进
|
|
570
|
-
"notify → confirm → cleanup"
|
|
571
|
-
|
|
572
|
-
|
|
570
|
+
"notify → confirm → cleanup"(照 running 处理会让用户等一个永不到来的收尾);
|
|
571
|
+
`stopped` 只提示手动清理。**状态定义以 `observability.check_status` 为单一
|
|
572
|
+
事实源**(`stalled` = 有 result.md、无 concluded、loop 均不存活)——此处不复述其判据。
|
|
573
|
+
`stalled` 用独立文案(其 result 由清理时保存,不复用 done 的"已留存"路径文案);扩展是唯一守卫
|
|
573
574
|
(CLI 的 cleanup 无状态防护)。**本轮明确不做**(防翻案):`--json` 输出模式、
|
|
574
575
|
resume 新命令面、env 回退配置、`runCli` 超时、启动路径继续优化(已在 1–2s 地板)、
|
|
575
576
|
给 stalled 加第三种动作。**契约例外**:扩展作为包内第一方消费者**直连**
|
|
576
577
|
`mv_cli.py`(实测 shim 61ms vs 直连 58–66ms,性能上零差异;按契约一致性记例外一行)。
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
578
|
+
**UI 通道按 mode 分级(2026-09-25 实测)**:dialog(`select`/`confirm`/`input`/`editor`)
|
|
579
|
+
全模式可用(RPC/web 走请求-响应子协议、阻塞等用户;不带 `timeout` 即不倒计时,
|
|
580
|
+
暂停点成立);`notify` 全模式可用(TUI = showStatus 行;pi-web = **追加进聊天流的
|
|
581
|
+
常驻行**,非瞬时提示);**`setEditorText` 仅 TUI**——pi-web 忽略(`pi-web/static/app.js`
|
|
582
|
+
注释「set_editor_text … ignored」+ SDK `ui-context.ts` 里是空实现)。⇒ **交付观看命令
|
|
583
|
+
必须有 notify 兜底**(预填只是增强,不能当唯一出口);需要分级时用 `ctx.mode`。
|
|
584
|
+
但 pi-web 实测**关掉 notify 弹窗即消失** ⇒ 观看命令还写一条 `pi.sendMessage`
|
|
585
|
+
(`customType: multi-viewers`、`display: true`)进消息流:持久可回滚复制,
|
|
586
|
+
代价 = 参与 LLM 上下文的一行(用户 2026-09-25 要求「message 流中也能显示」)。
|
|
583
587
|
被否决:A(handler 里 `sendUserMessage` 触发 LLM 回合改 spec——时序不可控)、
|
|
584
588
|
D(拆两条命令——把门禁成本转嫁用户;B1 变体/第三种即现形态)。
|
|
585
589
|
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<!-- 存档:docs/reviews/2026-09-25-multi-viewers-postfix-review.md
|
|
2
|
+
来源:一次真实多视角分析的 result.md 原文(未删改,仅加本头与下方说明)。
|
|
3
|
+
分析场次目录已随 cleanup 删除;文中消息编号(如 效率/0012、铁律/0004)不可再核验,
|
|
4
|
+
仅作溯源线索(与代码注释引用约定一致:行为以自描述为准)。 -->
|
|
5
|
+
|
|
6
|
+
# 存档说明
|
|
7
|
+
|
|
8
|
+
- **主题**:复验 0.8.0 这批改动 —— extension 合并后的实现(一单元三命令)、
|
|
9
|
+
P1–P6 修复是否到位、扩展层 harness 的覆盖是否有漏
|
|
10
|
+
- **场次**:`mv-mv-main-20260925-105614`(3 视角:效率 / 简单 / 铁律;真实 pi 讨论,
|
|
11
|
+
默认档 `mc-tools`、`forkMode=budget`、`maxMeeting=15`;三方共识收敛)
|
|
12
|
+
- **复验判定**:P1–P6 **逐条到位**;合并(P5)判定**净简化**(`findPackageRoot`
|
|
13
|
+
全仓 1 定义 1 调用、旧目录不存在);**未触碰唤醒路径** → 分析总时长期望零变化
|
|
14
|
+
- **本场新抓(多数是上一批引入的)**:
|
|
15
|
+
- **漏 A(最重要,接线 bug)**:`run_tests.sh --reuse` 的错误成功信号 ——
|
|
16
|
+
`last_ok()` 只扫任意一行 `^OK`,而 harness 输出追加在 python 的 OK 之后 →
|
|
17
|
+
harness 失败时它仍为真;指纹又无条件写 → 下次 `--reuse` 命中并 exit 0 而日志含失败。
|
|
18
|
+
修法 **②′**:真跑前清指纹、`rc==0` 才写回(不变式「指纹存在 ∧ 匹配 ⇒ 最近同源真跑全绿」)。
|
|
19
|
+
本仓落地时做了同款三连红绿验证(全绿→指纹在;环境性失败→rc=1 且指纹已清;随后
|
|
20
|
+
`--reuse` 必须未命中→真跑)
|
|
21
|
+
- **D2**:启动通知「已预填进输入框」在 pi-web 为**不实断言**(`setEditorText` 仅 TUI)
|
|
22
|
+
- **B1**:`mv_cli.py` 两条人类行复用 `[start]` 契约前缀;**B2**:五处出路提示引用裸
|
|
23
|
+
`mv.sh`(不在 PATH 上,出路不可执行)
|
|
24
|
+
- **7 类现存分支零覆盖** + 两项装置缺口:**sid 注入零断言**(漏注入 = say/finish
|
|
25
|
+
静默定位失败而全绿)、harness 的 `new URL().pathname` 不还 percent-encoding
|
|
26
|
+
- **D1/D3** 文档漂移(AGENTS「extension × 2」;决策 22 复述 stalled 判据)
|
|
27
|
+
- **落地**:`69a415a`(六文件一次收口;harness 28 → 44 断言,含 sid 装置与
|
|
28
|
+
`findPackageRoot` 直断 throw;先红后绿:去掉 sid 注入 → 3 条断言失败)
|
|
29
|
+
- **本场暴露的用户可见事实**(不在代码里):① 门禁暂停点**真的停住**(用户实测);
|
|
30
|
+
② pi-web 的 `notify` **关掉弹窗即消失** → 后续加第三交付出口
|
|
31
|
+
`pi.sendMessage`(custom_message 进消息流,`e92eacc`)
|
|
32
|
+
|
|
33
|
+
# 0.8.0 复验 · 三方共识结果
|
|
34
|
+
|
|
35
|
+
**主题**:复验 0.8.0 这批改动:extension 合并后的实现(一单元三命令)、P1–P6 修复是否到位、
|
|
36
|
+
扩展层 harness 的覆盖是否有漏——只提意见,不改代码。
|
|
37
|
+
**参与者**:效率 / 简单 / 铁律(3 视角;meeting 自由讨论 + round-robin 全体 pass)。
|
|
38
|
+
**方法**:只读核对、每项带 `文件:行`;零 LLM 验证(harness 实跑、/tmp 逐字复刻门逻辑复现、
|
|
39
|
+
grep/源码核对);**未修改任何文件、未运行真实分析**。
|
|
40
|
+
**场次**:`mv-mv-main-20260925-105614`(fork 模式 budget;mc-tools 档)。
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 0. 结论摘要
|
|
45
|
+
|
|
46
|
+
1. **P1–P6 行为修复逐条到位,合并(P5)判定为净简化**:`extensions/multi-viewers/` 现为单一
|
|
47
|
+
单元、三命令(`index.ts` + `shared.ts`),逐字重复消失;**未触碰唤醒路径**
|
|
48
|
+
(48–82s/唤醒的大头一行未改,agent 注入内容未变)→ 分析总时长期望零变化,收益落在
|
|
49
|
+
人类交互层与维护期(效率/0001)。
|
|
50
|
+
2. **本场共发现**:2 处新缺陷(漏 A:`--reuse` 错误成功信号;漏 B:P4 加载期分支无覆盖)、
|
|
51
|
+
3 处设计符合度偏差(D1/D2/D3)、2 处职责边界问题(B1/B2)、7 类 harness 覆盖漏 +
|
|
52
|
+
2 项装置缺口(sid 断言、percent-encoding)。**其中漏 A 的完整修法 ②′ 是本场最重要产出**
|
|
53
|
+
(三方认账,效率/0012、简单/0012)。
|
|
54
|
+
3. **②′(铁律 0004 §二)**:`run_tests.sh` 的"失败不写指纹"(原方案②)**不能消除漏 A**——
|
|
55
|
+
同源码环境性失败会残留旧绿指纹,`--reuse` 仍命中 exit 0 而日志含失败(已复现)。
|
|
56
|
+
完整修法 = 真跑开始前 `rm -f "$FP_FILE"`、结束 `rc -eq 0` 才写回(:90 现为无条件写)。
|
|
57
|
+
不变式「指纹存在 ∧ 匹配 ⇒ 最近一次同源真跑全绿」,单一 rc 判据、零文本解析、覆盖中断。
|
|
58
|
+
4. **净复杂度账为正(变简)**:S1 消 ≈35–50 行同构 spawn 样板、S2/S3 消软分支与 no-op、
|
|
59
|
+
批量条目多为 1–6 行改动;本批**零新装置**(新增能力仅 = 假 python3 多记一个 env 变量 +
|
|
60
|
+
导出 1 个函数 + 一个字符串常量)。
|
|
61
|
+
5. **执行**:六文件一次编辑 → 一次 `/reload` → **约 5 分钟验证**(`run_tests.sh --force` +
|
|
62
|
+
零 LLM 门禁路径 + ②′ 红绿 ~10s);**不专门真跑**(改动不触唤醒路径/协议注入/分析逻辑)。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 1. 复验判定(0.8.0 本批 = dfe71cc 合并与 P1–P6)
|
|
67
|
+
|
|
68
|
+
| 项 | 判定 | 依据(抽核) |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| **P1** stalled 并流 | ✓ 到位 | `index.ts` 状态分派:running 早退,`done|stalled` 并流到 confirm→cleanup;harness 断言 stalled 不再落 running |
|
|
71
|
+
| **P2** stalled 独立文案 | ✓ 到位 | stalled 不复用 done 的 result 路径文案;harness 断言不含 done fallback 文本 |
|
|
72
|
+
| **P3** 删与暂停点矛盾的旧注释 | ✓ 到位 | 文件头现为「取消则不启动并保留 spec」,无「先取消再自行 start」旧句 |
|
|
73
|
+
| **P4** 删单机回退、加载期响亮失败 | ✓ 到位 | 全仓 `FALLBACK_ROOT` 0 命中;`shared.ts` 找不到包根即 throw(行动性信息);覆盖缺口见 §2.2 |
|
|
74
|
+
| **P5** 合并为一单元三命令 | ✓ 到位 | `findPackageRoot` 全仓 1 定义 1 调用;旧 `multi-viewers-say/` 目录不存在 |
|
|
75
|
+
| **P6** 取消提示 + harness 进仓库 | ✓ 到位 | 取消提示含「当前 pi session 内」;`tests/extension_harness.ts` 被 `run_tests.sh` 接入(缺 bun 可见跳过、`MV_REQUIRE_BUN=1` 严格、失败置 rc=1) |
|
|
76
|
+
|
|
77
|
+
**复验实测**:harness **28 通过 / 0 失败**(口径更正:效率/0001 的 29 = `grep -c 'check('`
|
|
78
|
+
含 1 处函数定义;运行汇总为 28);harness 墙钟 **0.113–0.155s**(3 次,本机)——占全量
|
|
79
|
+
(~250s 口径)≈0.05%,"反对 unittest↔harness 并行"由预测变为数据支撑。
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 2. 本场新发现(按类别)
|
|
84
|
+
|
|
85
|
+
### 2.1 漏 A:`--reuse` 的错误成功信号 → 修法 ②′(最重要)
|
|
86
|
+
|
|
87
|
+
- **机制**:`last_ok()` 只扫全日志任意一行 `^OK`(`run_tests.sh:50`),harness 输出**追加**在
|
|
88
|
+
python `OK` 之后 → harness 失败时 `last_ok` 仍真;指纹无条件写(`:90`)→ 下次 `--reuse`
|
|
89
|
+
命中(`:56-57`)并 **exit 0** 而缓存日志含 harness 失败。
|
|
90
|
+
- **原方案②的残余洞(铁律 0004 复现)**:仅"失败不写指纹"时,**同源码环境性失败**
|
|
91
|
+
(`MV_REQUIRE_BUN=1` 缺 bun / 中断 / harness 并发)会残留上一轮旧绿指纹 → 仍命中 exit 0。
|
|
92
|
+
已用**逐字复刻的门逻辑**在 /tmp 复现(`→ REUSE 命中:exit 0(而 LAST_LOG 含 harness 失败)`)。
|
|
93
|
+
- **②′**:真跑前 `rm -f "$FP_FILE"`;结束 `rc -eq 0` 才写回。`last_ok` 可留但**不能当 ② 的
|
|
94
|
+
替代**(它挡不住本反例);②′ 后其定位退为"遗留指纹/篡改面"。
|
|
95
|
+
- **红绿核验(3 条命令,同参数隔离源指纹)**:① module 全绿 → FP 存在;②
|
|
96
|
+
`env PATH=/usr/bin:/bin MV_REQUIRE_BUN=1` 同参数跑 → rc=1 **且 FP 不存在**;③ `--reuse`
|
|
97
|
+
→ 必须"未命中→真跑"(现状会命中 exit 0)。
|
|
98
|
+
- **三方裁决**:效率/0012、简单/0012 均认账采纳(补洞 + 单一 rc 判据 + 覆盖中断)。
|
|
99
|
+
|
|
100
|
+
### 2.2 漏 B:P4 加载期失败分支无覆盖 → 内联(可裁)
|
|
101
|
+
|
|
102
|
+
- `findPackageRoot` 未导出(`shared.ts:29`),harness 内 import 成功即无法触发;子进程负例
|
|
103
|
+
需**复制** `shared.ts` → 违反「测试对象 = 生产对象」(副本漂移)。
|
|
104
|
+
- **裁定**:导出该函数直调、**只断言 throw**(至多含稳定标识 `pi-multi-viewers`)、不锁散文;
|
|
105
|
+
**残差明账**(锁函数、不锁加载期接线;坏掉症状=三命令消失,响亮)。
|
|
106
|
+
- **内联本批**(不做事后追加:验证是批次级成本,事后追加=第二次验证循环);优先级低,若砍
|
|
107
|
+
则记账留与下次改动同批(效率/0007 自我更正)。
|
|
108
|
+
|
|
109
|
+
### 2.3 设计符合度偏差(D1/D2/D3)
|
|
110
|
+
|
|
111
|
+
- **D1**:`AGENTS.md:236`「extension × 2」未随 P5 合并同步(同段已写"一个扩展单元内注册
|
|
112
|
+
三命令");`git show dfe71cc` 证实该行未动——"改了实现忘改抄本"的漂移。简单认领,改一行。
|
|
113
|
+
- **D2**:启动通知「已预填进输入框,按 Enter 执行」在 **pi-web 为不实断言**
|
|
114
|
+
(决策 22:577-581 已写明 `setEditorText` 仅 TUI;复核 `pi-web/static/app.js:1907`、
|
|
115
|
+
`ui-context.ts:456` 空实现)——与 0.8.0 首用事故(预填被忽略)同源。
|
|
116
|
+
**修法:中性单串**(如「复制执行;TUI 下已预填」),**不引 `ctx.mode` 分支**(无 mock/断言
|
|
117
|
+
增量,断言体零改动,仅标签措辞随统一改名处理);`setEditorText` 调用保留(TUI 增强)。
|
|
118
|
+
- **D3**:决策 22:570 对 `stalled` 的复述("无存活 loop 的静止态")宽于单一事实源
|
|
119
|
+
(`observability.check_status`:**有 result.md**、无 concluded、loop 均不存活)——按字面
|
|
120
|
+
把 `stopped` 也涵盖。**扩展文案不改**(正常路径恒真),**决策 22 引用化**。
|
|
121
|
+
- **边界观察(简单/0007 读码定案,不进本批)**:status 判"有结果"看 `git log --all`,而
|
|
122
|
+
`preserve_result_md` 读 `HEAD:result.md`(`meeting_fs.py:1238`)→ 侧分支/删除场景可达性低;
|
|
123
|
+
**将来统一必须偏 loud**(危险方向 = 清理静默丢产物),不得选静默侧。
|
|
124
|
+
|
|
125
|
+
### 2.4 职责边界问题(B1/B2)
|
|
126
|
+
|
|
127
|
+
- **B1**:`mv_cli.py:368/371` 两条人类行复用 `[start]` 契约前缀且带路径(唯一契约清单外同前缀
|
|
128
|
+
行)——"方括号=机器契约"被稀释。修法:**去方括号**(非新增 `[spec]` 前缀);已核
|
|
129
|
+
`test_mv_cli.py:312` 只断子串、零测试连锁。
|
|
130
|
+
- **B2**:`index.ts:67/72/86/129/198` 五处出路提示引用裸 `mv.sh`,而 `which mv.sh` 为空、
|
|
131
|
+
README:55 教的是安装绝对路径 → 出路不可执行。修法:`shared.ts` 增**常量**
|
|
132
|
+
`MV_SH = \`bash ${PACKAGE_ROOT}/scripts/mv.sh\``,五处引用(PACKAGE_ROOT 现为模块私有,
|
|
133
|
+
常量化非抽象层)。
|
|
134
|
+
|
|
135
|
+
### 2.5 harness 覆盖漏与装置缺口
|
|
136
|
+
|
|
137
|
+
**漏场景(7 类,均为现存分支)**:① MV `!specDir`(rc=0 无 `[prepare] spec=`)② MV
|
|
138
|
+
`!watch` ③ FIN 状态读取失败整支 ④ FIN cleanup 失败 ⑤ SAY find-dir 成功 + sayer 失败
|
|
139
|
+
⑥ SAY 空输出("插话已发送")⑦ `[start] dir=` 缺失(按 §3-S2 定案 (b) 写作"→ 报错")。
|
|
140
|
+
补方式按"追加用例"(两处必然例外:S2 改成功夹具、sid 装置改 calls 输出格式);断言名带
|
|
141
|
+
分支标识(替代另立清单表——清单=第二抄本必漂移);现有断言一并统一改名(纯改名零行为)。
|
|
142
|
+
|
|
143
|
+
**装置缺口(本场新增两项)**:
|
|
144
|
+
- **sid 传递零断言**:假 python3 只记 argv(`harness:25-31`);sid 是 `mv-<sid>-*` 命名与
|
|
145
|
+
find-dir 的唯一钥匙,漏注入 = say/finish **静默定位失败**而 28 条全绿 → 假装置多记
|
|
146
|
+
`$PI_SESSION_ID` + 各命令断言(**升为防静默同级**)。
|
|
147
|
+
- **`HERE = new URL(…).pathname`**(`harness:18`)不还原 percent-encoding——实测把
|
|
148
|
+
`extensions/` 与 harness 复制到 `/tmp/space test/repo` 运行即报
|
|
149
|
+
`Cannot find module '/tmp/space%20test/repo/…'`;改 `fileURLToPath`(与生产 `shared.ts`
|
|
150
|
+
同处理)。
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 3. 冻结的批次清单(六文件、一次收口)
|
|
155
|
+
|
|
156
|
+
**编辑顺序**(依赖者后写;S1 与 harness 无强依赖,真依赖仅 S2→#7 场景、D2 措辞→标签文案):
|
|
157
|
+
|
|
158
|
+
1. `extensions/multi-viewers/shared.ts`:S1 三 spawn 样板合一(原语 ≤~15 行、三调用点语义
|
|
159
|
+
逐一保留:stderr 合并与否 / rc vs ok / env 差异;**不顺手给 sayer 注入 sid**——现语义照抄)
|
|
160
|
+
+ `MV_SH` 常量 + 漏 B 导出(+头注释契约行同步)
|
|
161
|
+
2. `mv_cli.py`:S2 定案 **(b)**(`!dir` 并入 guard,删三元;dir 契约项保留)+ B1 去方括号
|
|
162
|
+
3. `extensions/multi-viewers/index.ts`:D2 中性文案 + B2 用 `MV_SH` + S3 删三处
|
|
163
|
+
`getArgumentCompletions: () => null`(`argumentHint` 保留——上游扩展命令映射未转发,
|
|
164
|
+
留着将来生效)
|
|
165
|
+
4. `tests/test_mv_cli.py`:S2 断言微调(如需)
|
|
166
|
+
5. `tests/extension_harness.ts`:7 类场景 + sid 装置/断言 + `fileURLToPath` + 断言名统一
|
|
167
|
+
(含现有断言)+ 漏 B 用例(导出直调、只断 throw)
|
|
168
|
+
6. `tests/run_tests.sh`:漏 A **②′**(真跑前清指纹 + `rc==0` 才写回)
|
|
169
|
+
|
|
170
|
+
**文档两行**:D1(`AGENTS.md:236`)、D3(决策 22 引用化)。**可裁顺序**:A 防静默 → B 用户
|
|
171
|
+
可见 → C 文档 → D 简化(漏 B 用例可裁,但要砍则记账留与下次同批)。
|
|
172
|
+
|
|
173
|
+
**批次约束**:零新装置、不触碰唤醒路径;验证 = `run_tests.sh --force`(~250s)+ `/reload`
|
|
174
|
+
零 LLM 走"假主题到门禁取消"(数十秒,顺带见到 D2/B2 新文案)+ ②′ 红绿(~10s)。
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 4. 明确不做 / 边界记录
|
|
179
|
+
|
|
180
|
+
- **不专门真跑**本批(改动全在扩展/CLI 人类输出/测试/文档层;不触唤醒路径与协议注入)。
|
|
181
|
+
- `last_ok()` 本批**不动**(对修复前遗留指纹有保护面;其精简留待下批评估)。
|
|
182
|
+
- **stalled+取消** 场景低价值可裁(两表均未列)。
|
|
183
|
+
- D3 的 `--all` vs `HEAD` 判据分叉:记为边界观察,**不进本批**;将来统一偏 loud。
|
|
184
|
+
- S2 的 (a)(删 `[start] dir=`)**留作将来因其它原因动契约时的清理候选**(本批不划算:
|
|
185
|
+
7–8 处连锁 + doc×3 漂移面;简单/0008、效率/0008 均持此记录)。
|
|
186
|
+
- `argumentHint` 上游未转发:保留,随上游补丁生效(不属本仓缺口)。
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 5. 收敛过程(消息索引)
|
|
191
|
+
|
|
192
|
+
- **效率**(0001–0012):墙钟四层账(唤醒是大头、extension 层非瓶颈)+ 省时账(P1 最大、
|
|
193
|
+
harness 性价比最高)+ 反对常驻/框架/并行/再弹 dialog;发现漏 A 并给出两方案 → 认账转 ②;
|
|
194
|
+
对漏 B"导出 vs 子进程"更正为内联;提供批内编辑顺序与"不专门真跑"验证分级;0002 提出
|
|
195
|
+
"补场景必须与修 `last_ok` 同批"的联动点。
|
|
196
|
+
- **简单**(0001–0012):S1/S2/S3 三处简化主张(spawn 样板合一、dir 半必填二选一、
|
|
197
|
+
删 no-op 字段)+ 反对过度抽象三条 + harness 覆盖表 + 主张"断言名带标识替代清单表";
|
|
198
|
+
读码定案 D3(`--all`/`HEAD` 分叉,比两边原表述更细);B2 提出 `MV_SH` 常量;
|
|
199
|
+
在 S2 (b)/(a) 与漏 B 口径上随证据两度更新立场。
|
|
200
|
+
- **铁律**(0001–0007):首轮以三条铁律逐项检视(P1–P6 到位 + D1/D2/D3 + B1/B2 + harness
|
|
201
|
+
六类 + 两项装置缺口);裁决漏 B(导出+只断 throw)、漏 A(②)、S2 语义、S1 验收三条;
|
|
202
|
+
认账并裁定 S2 定案 **(b)**;**发现并复现 ② 的残余洞,给出完整修法 ②′**(本场最重要产出);
|
|
203
|
+
复核 D2/D3 并记录 loud 方向;round-robin pass 收口。
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 6. 一句话结论
|
|
208
|
+
|
|
209
|
+
**0.8.0 的合并与 P1–P6 修复复验通过(harness 28/28、零行为反例);需在下一批补齐
|
|
210
|
+
②′(漏 A 完整修法)、7 类覆盖漏与 sid/路径两装置缺口、以及 5 处文案/文档偏差(D1/D2/D3、
|
|
211
|
+
B1/B2)——全部为 1–6 行级、零新装置、不触唤醒路径,一次收口 + 约 5 分钟验证即可。**
|
package/docs/reviews/README.md
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
| `2026-09-14-e2e24-extension-policy-review.md` | 扩展策略三档(默认 mc-tools)的实现与证据链 + `ctx_search` 价值评估 | **S1:缺 MC 的降级路径提前 return → 命令被截断(缺 model/print/注入、cwd 错)**;S1a 测试判别力不足;E2 报告缺"声明 vs 生效";E1 成本口径超出精度;T1–T3 文本矛盾;F5/F6/F7/S2/F9 解析链缺陷;**ctx_search 9 次调用全为问卷诱导、0 次决定性帮助、命中 1 条过期记忆** | `91a1171`(Batch 1+3)、`b9329fb`(Batch 2 删死代码)|
|
|
34
34
|
| `2026-09-14-e2e25-doc-drift-review.md` | 文档 vs 代码一致性(文档漂移)+ ctx_search 的**自然使用**观察 | **三处文档仍写"兜底 mv-* 并警告"而代码是无兜底、未匹配报错**(行为语义相反);README 缺 `--extension-policy`;报告字段列表过期;4 条缺失项;**根因 = 对实现的复述**(治本:引事实源不复制);自然使用观察:`ctx_search` **0 次**、historian 0 次 | `ba204ef` |
|
|
35
35
|
| `2026-09-25-multi-viewers-extension-review.md` | 把 `/multi-viewers` 改成 extension 这次的实现(extension 代码 / CLI 机器标记行契约 / 文档同步) | **P1:`stalled` 被当成 running → 让用户等一个永不到来的收尾**(唯一行为错误);P2 `[result]` 只在 done 打印;P3 头注释与暂停点自相矛盾;P4 `/root/pi-multi-viewers` 单机死回退;P5 两扩展逐字重复 ≈35 行且已漂移(→ 合并为一单元三命令);P6 取消提示缺 sid 提醒 + **扩展消费端零仓库内测试**;首用另暴露:主题带引号、观看命令只有预填一个出口 | 本批(合并 + P1–P6 + 首用两项 + harness 进仓库) |
|
|
36
|
+
| `2026-09-25-multi-viewers-postfix-review.md` | 复验 0.8.0 的 extension 合并与 P1–P6(含 harness 覆盖审查) | **P1–P6 逐条到位、合并净简化**;新抓 **漏 A:`run_tests.sh --reuse` 的错误成功信号**(harness 失败仍算绿 → 命中旧绿 + exit 0,修法 ②′ 清指纹 + rc==0 才写回);D2 通知里的不实断言(pi-web 忽略 `setEditorText`);B1/B2 契约前缀与不可执行出路;7 类现存分支零覆盖 + sid 注入与 percent-encoding 两装置缺口;D1/D3 文档漂移;S1–S3 简化 | `69a415a`(+ `e92eacc` 第三交付出口) |
|
|
36
37
|
|
|
37
38
|
## 环境口径(读报告时的背景)
|
|
38
39
|
|
|
@@ -16,14 +16,17 @@
|
|
|
16
16
|
*
|
|
17
17
|
* 与 CLI 的契约(标记行/退出码)与全部原语见 ./shared.ts。
|
|
18
18
|
*
|
|
19
|
-
* 观看命令交付 =
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* 观看命令交付 = **三个出口**(缺一不可,都是实测暴露的):
|
|
20
|
+
* ① `ctx.ui.setEditorText` 预填输入框(按 Enter 即执行;**仅 TUI**,pi-web 忽略)
|
|
21
|
+
* ② `notify` 带一份(即时可见;但 pi-web 上关掉弹窗即消失)
|
|
22
|
+
* ③ `pi.sendMessage` 写一条 custom_message 进消息流(**持久可回滚复制**;
|
|
23
|
+
* 代价 = 参与 LLM 上下文的一行)
|
|
22
24
|
*/
|
|
23
25
|
|
|
24
26
|
import {
|
|
25
27
|
findCurrentDir,
|
|
26
28
|
grab,
|
|
29
|
+
MV_SH,
|
|
27
30
|
runCli,
|
|
28
31
|
runSayer,
|
|
29
32
|
specListing,
|
|
@@ -35,7 +38,6 @@ export default function register(pi: any) {
|
|
|
35
38
|
pi.registerCommand("multi-viewers", {
|
|
36
39
|
description: "多视角协同分析:生成 spec → 你审阅 → 启动(零 LLM 流程)",
|
|
37
40
|
argumentHint: "<主题>",
|
|
38
|
-
getArgumentCompletions: () => null,
|
|
39
41
|
handler: async (args: string, ctx: any) => {
|
|
40
42
|
const topic = stripQuotes(args.trim());
|
|
41
43
|
if (!topic) {
|
|
@@ -64,12 +66,12 @@ export default function register(pi: any) {
|
|
|
64
66
|
"启动多视角分析?(现在暂停中,可在其它窗口修改 spec)",
|
|
65
67
|
`spec:${specDir}\n文件:${specListing(specDir)}\n\n` +
|
|
66
68
|
"需要修改就去改这个目录,改完点「确认」继续;\n" +
|
|
67
|
-
"点「取消」则不启动(spec 保留,可稍后
|
|
69
|
+
"点「取消」则不启动(spec 保留,可稍后 " + MV_SH + " --start)。",
|
|
68
70
|
);
|
|
69
71
|
if (!go) {
|
|
70
72
|
ctx.ui.notify(
|
|
71
73
|
`已取消,spec 保留在:${specDir}\n` +
|
|
72
|
-
`之后可在**当前 pi session
|
|
74
|
+
`之后可在**当前 pi session 内**用:${MV_SH} --start ${specDir}\n` +
|
|
73
75
|
"(外部终端执行时目录名不带 session id,插话/收尾命令定位不到它)",
|
|
74
76
|
"info",
|
|
75
77
|
);
|
|
@@ -80,20 +82,29 @@ export default function register(pi: any) {
|
|
|
80
82
|
const start = await runCli(["--start", specDir], cwd, sid);
|
|
81
83
|
const watch = grab(start.output, "[start] watch=");
|
|
82
84
|
const dir = grab(start.output, "[start] dir=");
|
|
83
|
-
if (start.rc !== 0 || !watch) {
|
|
85
|
+
if (start.rc !== 0 || !watch || !dir) {
|
|
84
86
|
ctx.ui.notify(
|
|
85
87
|
`启动失败:\n${start.output || "(无输出)"}\n` +
|
|
86
|
-
"可用
|
|
88
|
+
"可用 `" + MV_SH + " --status` 查看环境状态。",
|
|
87
89
|
"error",
|
|
88
90
|
);
|
|
89
91
|
return;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
// ④
|
|
94
|
+
// ④ 观看命令:三个出口(见文件头)——预填(仅 TUI)+ notify(即时)+ 消息流(持久)
|
|
93
95
|
ctx.ui.setEditorText(watch);
|
|
96
|
+
// 消息流里留一条持久记录:用户实测 pi-web 的 notify 会随弹窗关闭而消失,
|
|
97
|
+
// 关了窗口就再也找不到这行命令。custom_message 进会话(**参与 LLM 上下文**,
|
|
98
|
+
// 一行开销),在用户空闲时追加 → 立即显示、可回滚复制(agent-session.ts:
|
|
99
|
+
// 非 streaming + 无 triggerTurn → _appendCustomMessage,不触发回合)。
|
|
100
|
+
pi.sendMessage({
|
|
101
|
+
customType: "multi-viewers",
|
|
102
|
+
content: `多视角分析已启动:${dir}\n观看命令(复制执行,不进 LLM):\n${watch}`,
|
|
103
|
+
display: true,
|
|
104
|
+
});
|
|
94
105
|
ctx.ui.notify(
|
|
95
|
-
|
|
96
|
-
"
|
|
106
|
+
`分析已启动:${dir}\n` +
|
|
107
|
+
"观看(复制执行;TUI 下已预填进输入框):\n" +
|
|
97
108
|
`${watch}\n` +
|
|
98
109
|
"插话:/multi-viewers-say <文本> 收尾:/multi-viewers-finish",
|
|
99
110
|
"success",
|
|
@@ -104,7 +115,6 @@ export default function register(pi: any) {
|
|
|
104
115
|
// ---------------------------------------------------------------- 收尾
|
|
105
116
|
pi.registerCommand("multi-viewers-finish", {
|
|
106
117
|
description: "收尾:查状态 → 确认 → 清理分析目录(结果留存;摘要走对话)",
|
|
107
|
-
getArgumentCompletions: () => null,
|
|
108
118
|
handler: async (_args: string, ctx: any) => {
|
|
109
119
|
const sid = ctx.sessionManager.getSessionId();
|
|
110
120
|
const st = await runCli(["--status"], ctx.cwd, sid);
|
|
@@ -126,7 +136,7 @@ export default function register(pi: any) {
|
|
|
126
136
|
if (state === "stopped") {
|
|
127
137
|
ctx.ui.notify(
|
|
128
138
|
"分析已结束但未生成结果(状态 stopped)。" +
|
|
129
|
-
"要清理请自行运行
|
|
139
|
+
"要清理请自行运行 `" + MV_SH + " --cleanup`。",
|
|
130
140
|
"warning",
|
|
131
141
|
);
|
|
132
142
|
return;
|
|
@@ -179,7 +189,6 @@ export default function register(pi: any) {
|
|
|
179
189
|
pi.registerCommand("multi-viewers-say", {
|
|
180
190
|
description: "向正在进行的多视角分析插话(human 消息,各视角可见可回应)",
|
|
181
191
|
argumentHint: "<插话内容>",
|
|
182
|
-
getArgumentCompletions: () => null,
|
|
183
192
|
handler: async (args: string, ctx: any) => {
|
|
184
193
|
const text = args.trim();
|
|
185
194
|
if (!text) {
|
|
@@ -195,7 +204,7 @@ export default function register(pi: any) {
|
|
|
195
204
|
ctx.ui.notify(
|
|
196
205
|
"本 session 没有正在进行的多视角分析(cwd 下无 " +
|
|
197
206
|
`mv-${sid}-* 分析环境)。先用 /multi-viewers 启动,` +
|
|
198
|
-
"或改用
|
|
207
|
+
"或改用 `" + MV_SH + " --say <目录> \"<文本>\"` 显式指定。",
|
|
199
208
|
"error",
|
|
200
209
|
);
|
|
201
210
|
return;
|
|
@@ -25,8 +25,11 @@ const PKG_NAME = "pi-multi-viewers";
|
|
|
25
25
|
* **找不到就响亮失败**(评审 P4):此前回退到 `/root/pi-multi-viewers` 是单机
|
|
26
26
|
* 死分支——触发条件(包被拆散/复制)恰恰发生在开发机以外,回退只会把"包根
|
|
27
27
|
* 解析失败"变成更晚、更难诊断的失败。这里在**加载期** throw,错误信息给出行动。
|
|
28
|
+
*
|
|
29
|
+
* 导出仅供测试直调(断言 throw;不锁散文措辞)——加载期接线本身无覆盖,
|
|
30
|
+
* 残差明账:坏掉时症状 = 三个命令消失(响亮)。
|
|
28
31
|
*/
|
|
29
|
-
function findPackageRoot(start: string, pkgName: string): string {
|
|
32
|
+
export function findPackageRoot(start: string, pkgName: string): string {
|
|
30
33
|
let dir = start;
|
|
31
34
|
for (let i = 0; i < 8; i++) {
|
|
32
35
|
try {
|
|
@@ -52,26 +55,48 @@ const CLI = path.join(PACKAGE_ROOT, "mv_cli.py");
|
|
|
52
55
|
const SAYER = path.join(PACKAGE_ROOT, "human_sayer.py");
|
|
53
56
|
const OBSERVABILITY = path.join(PACKAGE_ROOT, "observability.py");
|
|
54
57
|
|
|
55
|
-
/**
|
|
56
|
-
|
|
58
|
+
/** 稳定的 CLI 入口(给用户的出路提示用)——裸 `mv.sh` 不在 PATH 上,
|
|
59
|
+
* 按此常量给绝对路径(评审 B2:出路必须可执行)。 */
|
|
60
|
+
export const MV_SH = `bash ${PACKAGE_ROOT}/scripts/mv.sh`;
|
|
61
|
+
|
|
62
|
+
/** 跑一次子进程并收输出。三处调用点的差异全部显式化(合并前是三份样板):
|
|
63
|
+
* `sid` 决定是否注入 PI_SESSION_ID(目录名与 find-dir 的唯一钥匙);
|
|
64
|
+
* `mergeStderr` 决定 stderr 是否并进同一缓冲(不并时必须 resume 掉,否则管道满阻塞)。 */
|
|
65
|
+
function capture(
|
|
57
66
|
args: string[],
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
): Promise<{ rc: number; output: string }> {
|
|
67
|
+
opts: { cwd?: string; sid?: string; mergeStderr?: boolean } = {},
|
|
68
|
+
): Promise<{ code: number; output: string }> {
|
|
61
69
|
return new Promise((resolve) => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
const env = opts.sid
|
|
71
|
+
? { ...process.env, PI_SESSION_ID: opts.sid }
|
|
72
|
+
: process.env;
|
|
73
|
+
const proc = spawn("python3", args, {
|
|
74
|
+
cwd: opts.cwd,
|
|
75
|
+
env,
|
|
65
76
|
stdio: ["ignore", "pipe", "pipe"],
|
|
66
77
|
});
|
|
67
|
-
let
|
|
68
|
-
proc.stdout.on("data", (d) => (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
let output = "";
|
|
79
|
+
proc.stdout.on("data", (d) => (output += d.toString()));
|
|
80
|
+
if (opts.mergeStderr) {
|
|
81
|
+
proc.stderr.on("data", (d) => (output += d.toString()));
|
|
82
|
+
} else {
|
|
83
|
+
proc.stderr.resume(); // 丢弃但必须消费
|
|
84
|
+
}
|
|
85
|
+
proc.on("close", (code) => resolve({ code: code ?? 1, output }));
|
|
86
|
+
proc.on("error", (e) => resolve({ code: 1, output: String(e) }));
|
|
72
87
|
});
|
|
73
88
|
}
|
|
74
89
|
|
|
90
|
+
/** 运行 mv_cli 一条命令;返回 { rc, output }(stdout+stderr 合并)。 */
|
|
91
|
+
export async function runCli(
|
|
92
|
+
args: string[],
|
|
93
|
+
cwd: string,
|
|
94
|
+
sid: string,
|
|
95
|
+
): Promise<{ rc: number; output: string }> {
|
|
96
|
+
const r = await capture([CLI, ...args], { cwd, sid, mergeStderr: true });
|
|
97
|
+
return { rc: r.code, output: r.output.trim() };
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
/** 取机器可读标记行的值(`[label] value`);没有 → null。 */
|
|
76
101
|
export function grab(output: string, label: string): string | null {
|
|
77
102
|
for (const line of output.split("\n")) {
|
|
@@ -116,37 +141,21 @@ export function specListing(specDir: string): string {
|
|
|
116
141
|
* 判据 = **退出码**(不是 stderr 文案——中文提示一改就静默失配,e2e16 F2):
|
|
117
142
|
* rc 0 → stdout 是绝对路径;rc 1 → 未找到(无同 sid 分析)。无降级通道
|
|
118
143
|
* (不会回退到"项目下最新"——那会插错分析,e2e16 F1)。 */
|
|
119
|
-
export function findCurrentDir(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
let out = "";
|
|
127
|
-
proc.stdout.on("data", (d) => (out += d.toString()));
|
|
128
|
-
proc.stderr.on("data", () => {}); // 原因只在 rc=1 时通知用户(见 handler)
|
|
129
|
-
proc.on("close", (code) => {
|
|
130
|
-
const dir = out.trim();
|
|
131
|
-
resolve(code === 0 && dir ? dir : null);
|
|
132
|
-
});
|
|
133
|
-
proc.on("error", () => resolve(null));
|
|
134
|
-
});
|
|
144
|
+
export async function findCurrentDir(
|
|
145
|
+
cwd: string,
|
|
146
|
+
sid: string,
|
|
147
|
+
): Promise<string | null> {
|
|
148
|
+
const r = await capture([OBSERVABILITY, "--find-dir"], { cwd, sid });
|
|
149
|
+
const dir = r.output.trim();
|
|
150
|
+
return r.code === 0 && dir ? dir : null;
|
|
135
151
|
}
|
|
136
152
|
|
|
137
|
-
/** 执行 human_sayer.py 一次插话。返回 { ok, output }。
|
|
138
|
-
|
|
153
|
+
/** 执行 human_sayer.py 一次插话。返回 { ok, output }。
|
|
154
|
+
* 现语义照抄(不注入 sid——sayer 用显式目录参数)。 */
|
|
155
|
+
export async function runSayer(
|
|
139
156
|
dir: string,
|
|
140
157
|
text: string,
|
|
141
158
|
): Promise<{ ok: boolean; output: string }> {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
145
|
-
});
|
|
146
|
-
let out = "";
|
|
147
|
-
proc.stdout.on("data", (d) => (out += d.toString()));
|
|
148
|
-
proc.stderr.on("data", (d) => (out += d.toString()));
|
|
149
|
-
proc.on("close", (code) => resolve({ ok: code === 0, output: out.trim() }));
|
|
150
|
-
proc.on("error", (e) => resolve({ ok: false, output: String(e) }));
|
|
151
|
-
});
|
|
159
|
+
const r = await capture([SAYER, dir, text], { mergeStderr: true });
|
|
160
|
+
return { ok: r.code === 0, output: r.output.trim() };
|
|
152
161
|
}
|
package/mv_cli.py
CHANGED
|
@@ -365,10 +365,10 @@ def cmd_start(spec_dir, extra):
|
|
|
365
365
|
# 条件 = 观测到一次 launch 失败,或将来引入 resume/retry 通道。
|
|
366
366
|
if fnmatch.fnmatch(os.path.basename(os.path.normpath(spec_dir)),
|
|
367
367
|
"mv-spec-*"):
|
|
368
|
-
print(f"
|
|
368
|
+
print(f"spec 已消费,删除(本工具生成形态): {spec_dir}")
|
|
369
369
|
shutil.rmtree(spec_dir, ignore_errors=True)
|
|
370
370
|
else:
|
|
371
|
-
print(f"
|
|
371
|
+
print(f"spec 已消费(保留未删——非 mv-spec-* 形态): {spec_dir}")
|
|
372
372
|
|
|
373
373
|
# 第 2 步:启动已有环境
|
|
374
374
|
if _call([PYTHON, START_DISCUSSION, "--dir", dir_path,
|
package/package.json
CHANGED