pi-multi-viewers 0.1.0

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.
Files changed (35) hide show
  1. package/AGENTS.md +192 -0
  2. package/README.md +153 -0
  3. package/docs/design.md +288 -0
  4. package/docs/examples/first-experiment/README.md +42 -0
  5. package/docs/examples/first-experiment/work-a/AGENTS.md +10 -0
  6. package/docs/examples/first-experiment/work-a/a/0001.md +65 -0
  7. package/docs/examples/first-experiment/work-a/a/0002.md +70 -0
  8. package/docs/examples/first-experiment/work-a/perspective.md +5 -0
  9. package/docs/examples/first-experiment/work-b/AGENTS.md +10 -0
  10. package/docs/examples/first-experiment/work-b/b/0001.md +100 -0
  11. package/docs/examples/first-experiment/work-b/perspective.md +6 -0
  12. package/docs/reviews/2026-09-10-e2e10-fork-source-modes-review.md +366 -0
  13. package/docs/reviews/2026-09-10-e2e11-forkmode-guards-review.md +229 -0
  14. package/docs/reviews/2026-09-10-e2e12-code-review.md +346 -0
  15. package/docs/reviews/2026-09-11-e2e13-code-review.md +284 -0
  16. package/docs/reviews/2026-09-11-e2e14-observability-review.md +216 -0
  17. package/docs/reviews/README.md +36 -0
  18. package/docs/test-methodology.md +258 -0
  19. package/extensions/multi-viewers-say/index.ts +156 -0
  20. package/fake_agent.py +120 -0
  21. package/human_sayer.py +144 -0
  22. package/human_viewer.py +215 -0
  23. package/meeting_core.py +255 -0
  24. package/meeting_engine.py +733 -0
  25. package/meeting_fs.py +1066 -0
  26. package/meeting_loop.py +606 -0
  27. package/package.json +41 -0
  28. package/prompts/multi-viewers.md +94 -0
  29. package/scripts/check-residue.sh +190 -0
  30. package/scripts/mv.sh +325 -0
  31. package/start_discussion.py +1485 -0
  32. package/templates/AGENTS.md.tpl +100 -0
  33. package/templates/agent.md.tpl +9 -0
  34. package/templates/gitignore.tpl +7 -0
  35. package/templates/spec-readme.md.tpl +87 -0
@@ -0,0 +1,346 @@
1
+ > **存档说明**:本文件是 2026-09-10 一次真实多视角自审(性能 / 简单 / 铁律
2
+ > 三视角)的 result.md 原文,主题为"code review 本项目的代码合理性"。
3
+ > 该轮抓到 13 项确认问题(守卫 fail-open、pgrep 自匹配、protocol.json 读取
4
+ > 散落、concluded 判定误报、engine 直做 I/O 等),修复见后续 commit。
5
+ > 报告中的 P1–P4/A1–A4/B1–B5/C1–C5 等编号为**当次讨论内部编号**,不可跨
6
+ > 文档核验;提及的路径(如 /tmp 现场)已清理。
7
+
8
+ # 代码评审结论(多视角协同分析)
9
+
10
+ > **分析**:pi-multi-viewers 代码合理性评审(性能 / 简单 / 铁律 三视角,meeting 协议)
11
+ > **日期**:2026-09-10
12
+ > **分析对象**:`/root/pi-multi-viewers` 全项目(核心实现 ~4,000 行 Python + wrapper + 352 测试)
13
+ > **产出**:评审意见清单(审阅型任务,未修改任何代码)
14
+ > **效力声明**:以下为**视角共识**,不是用户裁决——修复实施前需用户确认。
15
+
16
+ ---
17
+
18
+ ## 一、审查方式
19
+
20
+ 三视角独立通读核心实现(`meeting_core` / `meeting_fs` / `meeting_engine` /
21
+ `meeting_loop` / `start_discussion` / `human_viewer` / `human_sayer` / `scripts/mv.sh` /
22
+ `fake_agent`),并各自运行**实时探针**验证可疑点(非纯静态推理):
23
+
24
+ - 铁律视角:6 个探针(status 误判、pgrep 自匹配、锁态上溯、result_writer 双读/IndexError、
25
+ `.gitignore` 覆盖、锁免疫);
26
+ - 性能视角:热路径复刻(39/198/498 条 fixture)、fork 构建 cProfile 分解、ceiling 六场景探针、
27
+ 活现场内存测量;
28
+ - 简单视角:调用点穷尽清点(grep 枚举)、现场锁态观测、空 participants 回落核对。
29
+
30
+ 三视角对关键事实**独立复现并互相核对**(如守卫 fail-open 由三方各自观测到同一现象;
31
+ `agents=[]` 安全回落由简单提出、性能实测消费端确认)。
32
+
33
+ ---
34
+
35
+ ## 二、确认的问题(按批组织,含修复约束)
36
+
37
+ ### 批 A —— 守卫层(4 项)
38
+
39
+ #### A1【中】`_lock_git` 守卫 fail-open,且落点与 LLM 实际 cwd 错位(F7 = 性能#4 = 简单S4)
40
+
41
+ 三方独立观测(lock 生效期间实测):
42
+
43
+ ```
44
+ work-<agent>/.git 改名 .git.locked(锁生效)
45
+ 从 workdir: git rev-parse --git-dir → /root/pi-multi-viewers/.git ← 静默上溯到主仓库
46
+ LLM 的 bash: pwd → /root/pi-multi-viewers(fork 模式 spawn cwd)→ 主仓库可直接操作(含 GitHub push 远端)
47
+ git pull --rebase --autostash(锁态): 1054.6ms rc=0(真实网络 fetch) ← 对照解锁后 11ms
48
+ ```
49
+
50
+ - 设计前提是 `cwd = workdir`(helper 模式);fork 模式把 spawn cwd 改为主项目
51
+ (`meeting_loop.py:323`),**守卫挡的是 LLM 不去的门**;
52
+ - `_lock_git` docstring 声称"任何 git 操作失败(not a git repository)"——**被实证推翻**;
53
+ - **缓解事实(已核查)**:主项目 `.gitignore:4` 有 `discuss-*/`,`git check-ignore` 确认讨论
54
+ 目录整体被忽略 → 杂散 `git add -A` 无法把消息提交进主仓库,**"流程绕过"风险未打开**;
55
+ 真实剩余风险是杂散**写命令**作用于**用户主仓库**(比守卫原本要保护的讨论流程更贵)。
56
+
57
+ **修复(三方一致)**:
58
+ 1. spawn 注入 `GIT_CEILING_DIRECTORIES=<讨论目录>`——必须
59
+ `env={**os.environ, "GIT_CEILING_DIRECTORIES": base_dir}`(`Popen(env=)` 是整体替换,
60
+ 漏合并会丢 PATH);单点注入(`_run_wake_proc` 的 Popen),不做 loop 侧对称注入;
61
+ 2. 注释**范围化**:"使**从讨论 workdir 发起**的 git 操作 fail-closed;主项目仓库不在守卫范围,
62
+ 其约束归指令层"(不留全称断言);
63
+ 3. **数字与来源进 commit**(workdir 侧 pull 1054.6ms→1.0ms,锁态、本机),不进注释;
64
+ 4. 先红后绿测试:锁态下 `git rev-parse --git-dir`(cwd=workdir)须非零退出。
65
+
66
+ **裁决记录**:不把守卫扩展到主项目仓库(rename 式守卫对用户资产不可能 fail-closed,要设防需
67
+ 换机制类——沙箱/钩子;收益不支撑扩面)。条件重估:出现"主仓库被讨论 agent 杂散写"的
68
+ 可观测事件时,换机制类再议。
69
+
70
+ ceiling 修法验证(性能 6 项探针 + 铁律 3 项 + 真实布局复核):正常态不受影响 / 锁态
71
+ fail-closed / 多路径正常 / 不存在路径容错 / 项目根及其子目录不受影响 / pi 自身的 footer
72
+ git 调用(JS 自实现上溯 + 在 repoDir 上只读 `symbolic-ref`)不受影响。
73
+
74
+ #### A2【中】`pgrep -f` 自匹配 —— 与项目已固化约定冲突(F4)
75
+
76
+ - `start_discussion._loops_alive:853-857` 用 `pgrep -f "meeting_loop.py.*<base>..."`;
77
+ - 探针实证:无 loop 时也能匹配到调用者自身命令行;
78
+ - **加重定性**:`docs/test-methodology.md` §2 已明文记录该坑与对策(方括号技巧/精确 PID),
79
+ `scripts/check-residue.sh:98` 有现成实现——**违反项目自己已固化的约定**(设计符合度问题,
80
+ 无需等故障发生即应当修)。
81
+
82
+ **修复**:判据从"命令行文本正则"降为 **argv 字段相等**(简单给出 `/proc/<pid>/cmdline` 扫描
83
+ 形态,无子进程无正则,顺带删目录边界正则与 `re.escape`);性能实测 /proc 1.1ms vs
84
+ pgrep 5.9ms(附带改善,但不作为修改理由——理由是约定冲突)。
85
+
86
+ **实现约束**:比较必须与启动方同一构造 `os.path.join(base, "meeting_loop.py")`,且不得改变
87
+ launcher 构造式,否则"loop 存在但检测不到"会静默误报。测试面事实:现有测试无一断言"存活
88
+ loop 的 status",换形态后测试面零改动(新增测试时假进程须用同一构造)。
89
+
90
+ #### A3【低】`check-residue.sh` 注释与实现不符(F9,同类第 4 实例)
91
+
92
+ 注释称"过滤自身 shell PID(`$$` 及其父链)",实现是 `grep -v "grep\|bash -c"` 文本过滤。
93
+ 低优先(开发脚本非产品路径)。简单视角偏好改注释(零行为变化);若改成 `$$` 过滤则注释
94
+ 必须同批重写——不留"注释说机制 A、实现是机制 B"的状态。
95
+
96
+ #### A4【中】指令层冲突:项目 AGENTS.md 的 Git 准则 vs 工作协议(F8)
97
+
98
+ fork 模式的上下文含主项目文件自动发现(cwd/AGENTS.md 祖先发现),故 agent 的 system prompt 同时含:
99
+
100
+ | 来源 | 指令 |
101
+ |---|---|
102
+ | 主项目 `AGENTS.md`(自动发现) | "每次改动先更新本地 git(add+commit)"、"阶段性完成即推送" |
103
+ | `work-<agent>/AGENTS.md`(注入) | "不要执行任何 git 操作(commit/push/pull 由本地循环负责)" |
104
+
105
+ **切换叙事切不掉它**(尾部注入针对历史叙事,而主项目 AGENTS.md 是每次唤醒重新注入的常驻
106
+ 规则);在"改代码"类讨论里最尖锐(本场的"不执行 git 写操作"是 question.md 特有,通用形态
107
+ 不带这句)。
108
+
109
+ **修复(三方一致)**:协议侧(`templates/AGENTS.md.tpl`)一句话优先级声明——项目规则中与
110
+ 讨论流程冲突的 git 准则在本讨论中不适用。**反对**:(a) 移除主项目 AGENTS.md 注入
111
+ (产品上下文核心价值);(b) 任何"指令合并/过滤/优先级引擎"(为一句文本冲突发明状态 =
112
+ 过度设计)。分层依据:**机制性文本归脚本注入(协议),项目资产原样保留**。
113
+
114
+ ---
115
+
116
+ ### 批 B —— 读取路径净删(5 项,一次重构)
117
+
118
+ > 批 B 涉及 4 个文件(fs / engine / start_discussion / human_viewer),diff 规模最大,
119
+ > **建议单独立项评审**。
120
+
121
+ #### B1【中】`protocol.json` 读取散落 6 处 / 3 种读法 / 3 种失败语义(S1)
122
+
123
+ 调用点:`meeting_engine.py:57`(participants)、`:63`(result_writer)、
124
+ `meeting_loop.py:520`、`start_discussion.py:1051`/`:1077`、`human_viewer.py:36`。
125
+ 失败语义各不相同(engine 放任上抛 / loop `[fatal]` / start 报错 / viewer `{}`)。
126
+
127
+ **同根第二缺陷(探针实证)**:`result_writer()` 的
128
+ `proto.get("resultWriter", participants(workdir)[-1])` —— 默认值**无条件先求值**:
129
+ 每次调用读两遍 protocol.json(实测 2 次),且 `participants` 为空时即使 `resultWriter`
130
+ 已配置也抛 **IndexError**(`.get` 根本没机会返回)。
131
+
132
+ **修复(三方一致)**:
133
+ - fs 出**单形态原语** `read_protocol(bare) -> dict`(`HEAD:protocol.json`;任何失败返回 `{}`)
134
+ ——**无 `ref` 参数、无双形态**(单形态 = 消除选择;"每调用点选源并写理由"的契约随之消解);
135
+ - 6+1 处全走它;`human_viewer._protocol` 删除;
136
+ - **失败语义契约**:原语统一 `{}` + **各调用点一行显式政策**(loop:`if not proto: [fatal]
137
+ exit 1`;engine `participants()`:`if not agents: raise`(响亮失败);`check_status`:
138
+ **无需政策分支**——core 空 dict 守卫使 `aggregate_mode({})` → `"meeting"`,自然落
139
+ running/stalled);
140
+ - **数据源定案 = 单源 bare HEAD**(engine 撤掉本地文件读路径——不是"新增第二源"而是"撤掉
141
+ 文件读",engine 始终只有一条数据源,只是换成共享事实那条)。附带收益:消除"LLM 改本地
142
+ protocol.json → engine 看局部视图"的风险(影响面归零);
143
+ - **实现面**:`participants`/`result_writer` **保留 `workdir` 签名**,函数体内自推
144
+ `bare = dirname(workdir)/repo.git` → **零调用点签名改动**(调用点:engine 3 处 +
145
+ `human_sayer.py:55` 跨模块 1 处);
146
+ - 测试面核实:四个 fixture(`test_dir_forms` / `test_main_paths` / `test_meeting_engine` /
147
+ `test_meeting_loop`)的 setup 全部 commit+push protocol.json 到 bare,**测试面不受影响**。
148
+
149
+ #### B2【中】`check_status` 的 concluded 判定:文本 grep 误判 + 双定义(F1 = S2)
150
+
151
+ - 实现(`start_discussion.py:883-889`):`git grep -l "^type: concluded$" HEAD -- :(exclude)human/*`
152
+ ——**行文本 grep、扫 HEAD 全树**;
153
+ - 注释声称"读消息文件 frontmatter 的 type(不用 git grep 全文——正文出现会误匹配)"——**被实证推翻**;
154
+ - **精确触发条件(探针修正)**:需 `type: concluded` **整行独占**才触发(散文提及不触发;
155
+ 代码块引用 frontmatter 示例、协议片段粘贴是现实来源)。探针确认 **`result.md` 自身被命中
156
+ 即返回 `done`**;检索面是 HEAD 全树(唯一排除 `human/*`);
157
+ - **影响链**:`check_status` 误报 done → `--wait` 的终止分支(stalled/stopped)被跳过 →
158
+ 落到 `incremental.done`(真实推导 = False)→ **10s 轮询无上界**(T3/e2e7 修复要消灭的
159
+ 失败类换形态回归);
160
+ - **"任何消息含 concluded"语义无消费方(推演确认)**:唯一写者是 `write_protocol_signal`
161
+ (`next_msg_id` → 恒为该 agent 最新消息文件),写入后 `agent_loop` 下一轮即退出(重启亦然)
162
+ → "任一 agent 末条 == concluded"与"信号存在"在可达状态下等价,grep 的宽语义只有假阳性。
163
+
164
+ **修复(三方一致)**:删 grep 实现,**复用既有 `aggregate_mode`**(`check_status` 内两步组装:
165
+ fs 原语取 participants + `aggregate_mode(bare, agents) == "concluded"`)。
166
+
167
+ - **无第三份读取器、不新增引擎谓词**(`is_concluded(bare)` 被否:engine 已有单一定义,
168
+ 且新谓词会引入 engine 的第二条数据源路径);不 import `human_viewer`(状态判定不挂展示模块);
169
+ - done **保留两条件**(result.md 存在 **且** concluded),注释依据(简单给出):
170
+ **stalled 分支存在的全部意义就是"报告已写、流程信号未写"**——只看 concluded 则 stalled
171
+ 与 stopped 合并(丢状态),只看 result.md 则 stalled 直接消失(review5 A5 原始缺陷);
172
+ 两个条件各对应一个已有分支 = check_status 的**状态图需要**;
173
+ - `result.md` 条件**不进 engine**(只有 check_status 用,搬进去 = engine 携带 CLI 专属分支)。
174
+
175
+ 成本(性能实测):收敛后 `--wait` 每轮 +6.3ms(含 participants from bare)= **0.063% 单核**;
176
+ **成本不构成选型依据**。双读登记口径:"**每轮两次 `aggregate_mode` 全量读**(check_status
177
+ 一次纯新增 + incremental 一次兼作显示)"——进修复 commit(附来源),不散落设计文档。
178
+
179
+ #### B3【中】engine 直做 fs 的 I/O,且同函数内读写路径不一致(F2)
180
+
181
+ - 清单(`meeting_engine.py`):`:57`/`:63` 裸 `json.load(open(protocol.json))`;
182
+ `:102-145` `_cat_batch` 内 `Popen git cat-file`(git I/O 落在 engine);`:421`/`:429`
183
+ `os.remove`;`:444` `open(path, "w")`;`:496-499` `os.path.exists/getsize`;
184
+ `:529` `open(result_path, "w")`(兜底写 result.md);
185
+ - **自身注释即依据**:`commit_new_files:416` 写着"IO 归 fs(L16)"并走 `read_message`
186
+ ——同一函数里**读走 fs、写/删走裸 os**(`finalize_discussion` 同理)。
187
+
188
+ **修复(三方一致,三条边界)**:
189
+ 1. fs 补**原语** `cat_batch(bare, paths)` / `remove_message` / `write_text`(只搬原语,
190
+ **不做"快照对象"跨函数传**——跨函数状态比多一次调用复杂,且诱导缓存);
191
+ 2. **批量读形态原样保留**(`_each_agent_messages` 每轮循环顶都跑;性能红线:逐条
192
+ `git_show` = **16.8× 回归** 733ms vs 43.6ms@498 条)+ **性能理由随函数迁移**
193
+ (docstring 那句"不用每消息一次 git_show(O(n) subprocess)"不得丢失);
194
+ 3. engine 保留组装职责(吃 fs 原语返回的原始内容,自己派生判定),fs 只出原语。
195
+
196
+ #### B4【低】两个 git 执行入口,quotepath 加固不一致(F3)
197
+
198
+ `meeting_fs.run_git:25` 带 `-c core.quotepath=false`(注释自述"一处统一,全部命令生效");
199
+ `start_discussion.run_cmd:37` 是裸 git(被 `check_status` 的 log/grep、`--wait` 的 show 等使用)。
200
+ 当前调用恰好不解析路径输出(未触发已知 bug),但加固只覆盖一半,是同类问题的潜在根因。
201
+
202
+ **修复**:**只留一个**——读路径(log/grep/show)改走 `fs.run_git`;`run_cmd` 退回"一次性环境
203
+ 命令"(init/clone/config/pgrep 改 /proc 后只剩这些)。与 F2 同批。
204
+
205
+ #### B5【低】`_unlock_git` 与 `recover_git_lock` 逐字重复(S3)
206
+
207
+ `meeting_loop.py:207-216` 与 `:219-225` 条件/目录名/rename 逐字相同,仅差一行 log。
208
+
209
+ **修复**:合并为 `restore_git_lock(workdir) -> bool`(**纯函数 + 返回值**,log 由唯一需要它
210
+ 的调用点决定 → 保住"只在确实恢复时打";finally 忽略返回值,启动 `if restore: log(...)`)。
211
+ 保留在 `meeting_loop.py`(配对可见性,L4 原理由仍有效,不搬 fs)。
212
+
213
+ ---
214
+
215
+ ### 批 C —— 注释与文档(4 项)
216
+
217
+ #### C1【低】`agent_loop` 注释"其余判定全部派生"过强(F5)
218
+
219
+ 注释(`meeting_engine.py:583-584`)称"循环顶一次全量 bare 读取…其余判定全部派生";
220
+ 实际 `human_msg_count` / `_stall_elapsed` 每轮各自 `ls-tree`,`rr_next_speaker` 在 RR 分支另读。
221
+
222
+ **修复**:注释改成"消息列表类判定派生;计数/时序类单独读(各自无状态)"——零成本事实对齐。
223
+ **不做收编**(三方一致:为 ~3ms/轮 跨函数传快照并约束调用顺序不划算)。
224
+
225
+ #### C2 "声明 vs 保证"类目(四实例,随批扫查)
226
+
227
+ 同类病症:**注释/文档的断言强度或范围超过机制保证**。
228
+
229
+ | 实例 | 声明 | 实际 |
230
+ |---|---|---|
231
+ | F1 `check_status` 注释 | "不用 git grep 全文——正文出现会误匹配" | 就是 git grep 全文,且断言不准 |
232
+ | A1 `_lock_git` 注释 | "任何 git 操作失败" | 静默换仓库成功(上溯) |
233
+ | C1 `agent_loop` 注释 | "其余判定全部派生" | 3 处每轮各自重读 |
234
+ | A3 `check-residue.sh` 注释 | "过滤自身 shell PID($$ 及其父链)" | 文本模式过滤 |
235
+
236
+ 这正是 `docs/test-methodology.md` 方法 11 的领域——但此前主要落在**制品文本**(文档/日志),
237
+ **代码内注释未被同一标准扫过**。建议随批做一次全称断言扫查(搜"任何/所有/全部/唯一/始终/
238
+ 总是/永不"),逐条对照机制回答"凭什么成立"——**不允许留下回答不了的全称断言**。形态:
239
+ 不新增工具/文档,结果直接以注释改动进修复 commit。
240
+
241
+ (自我更正记录:铁律视角在本讨论中自己也犯过一次"表述过宽"——F1 首轮说"正文引用很自然"
242
+ 经探针后收窄为"需整行独占";另一次是把己方推演写成他人陈述,经核对原文后更正。
243
+ 这类错误的自然发生率就是这么高。)
244
+
245
+ #### C3 数字三分法与"一个数字一个家"
246
+
247
+ | 类型 | 例 | 去处 |
248
+ |---|---|---|
249
+ | **结构性质**(不随数据漂移) | "批量读一次进程 / 逐条读 O(n) 子进程" | **docstring**(即"理由随迁"那句) |
250
+ | **修复依据的实测值** | 733ms/43.6ms、16.8×、1054.6ms→1.0ms、5.9ms→1.1ms | **commit message**(带口径 + 来源) |
251
+ | **长期可复用的口径数字** | 构建 181ms/55MB、pi 会话 RSS、校准比 1.66× | **`docs/design.md` 口径节** |
252
+
253
+ 判据:**会在下一次评审中被引用吗?** 会 → design.md;只解释本次为何这样改 → commit。
254
+ 边界:**一个数字只留一个"家"**(commit 是溯源记录、design.md 是长期引用点,不并存两份
255
+ 权威值;长期引用点漂移时就地更新带新口径)。
256
+
257
+ **术语修正**:上述第一类**不称"不变量"**——该项目 `不变量` 已专指 **I1–I5**(fork 源产物
258
+ 结构约束,docstring / design.md / 测试名三处对齐),一词两义会造成歧义。改称**结构性质**。
259
+
260
+ #### C4 文档修正(3 处)
261
+
262
+ 1. **F6**:`docs/design.md` §二 `forkSourceDropped` 条未写明**仅 budget 模式写该字段**
263
+ (代码 `meeting_fs.py:772-777`:两字段都在 `if mode == "budget":` 分支内)——读者会以为
264
+ 三模式都有。一行修复(建议措辞见 性能/0009 §4);
265
+ 2. **措辞修正**:design.md §三 被否决方案行"唯一收益是内存峰值"被实测推翻(json.loads 占
266
+ 构建 **49%** 耗时、**90%** 解析条目被弃用)——改"收益 = 内存峰值 + 解析时间"(否决结论
267
+ 不变,触发条件不变);
268
+ 3. **规模口径补行**:design.md §四 只记 loop 侧内存(165MB 量级),漏掉主导项——
269
+ **pi 会话常驻内存 ~1GB/个**(实测 935/1157/954MB,`ps -o rss`,本机 16GB、当前会话长度
270
+ 含扩展;触发:N≥8 或内存紧张时复核)。**与现有 loop 侧行并列**(差一个量级,读者需两行)。
271
+
272
+ #### C5 合规项(修复完成判据的一部分)
273
+
274
+ `AGENTS.md` Git 准则第 5 条:"对协议行为、产品形态的任何修改,必须同步 README 与相关模板。"
275
+ 本批至少两处触发:F8(协议模板加声明 → 同步 `templates/AGENTS.md.tpl`)、A1/C1/C4 的
276
+ 行为描述修改(同步 README 机制表 / AGENTS.md 对应节)。修复单里"同步项"与"先红后绿测试"
277
+ **并列**为完成判据,不是事后跟进。
278
+
279
+ ---
280
+
281
+ ## 三、确认无问题(量化支撑)
282
+
283
+ 1. **轮询热路径**(`agent_loop` 空闲路径):26.5ms/轮/agent、8 个 git 子进程;扩展性线性
284
+ (39→8.2ms / 198→20.1ms / 498→52.6ms ≈ 0.11ms/条 + 3ms 固定);3 agent 并发 ≈ 4% 单核。
285
+ 触发轮有界(最坏 33.8ms)。当前规模无瓶颈。
286
+ 2. **职责边界正例**:`meeting_core` **零 import**(纯判定无 I/O);`fake_agent`/`meeting_loop`
287
+ 通过注入 responder 复用唯一状态机;`human_sayer` 写路径全走 fs 原语;`human_viewer`
288
+ 无写路径;`mv.sh --status/--wait/--cleanup/--prepare` 全部委托 python(无 bash 复刻);
289
+ 视角校验两入口同源(`_discover_viewers` 单一实现);I1–I5 有测试且 I4 用 replay oracle
290
+ (强断言形式)。
291
+ 3. **F1 修复的成本不构成障碍**(三种实现同量级,`--status`/`--wait` 按需调用)。
292
+ 4. **空 participants 安全回落**(`aggregate_mode({})` → `"meeting"`,消费端实测
293
+ `incremental(bare, [])` → `done=False`——无需兜底分支)。
294
+ 5. **单源 bare 读与守卫无交互**:bare 是 `base/repo.git`(独立目录),锁只动
295
+ `base/work-X/.git` → 对锁免疫。
296
+
297
+ ---
298
+
299
+ ## 四、记录项(不修 / 维持 defer,附触发条件与观测点)
300
+
301
+ | 项 | 实测/性质(口径) | 触发条件(观测点) |
302
+ |---|---|---|
303
+ | fork 构建解析时间 O(源) | json.loads 109ms = **49%** 构建耗时;**90%** 解析条目被预算弃用;15MB 源 → 224ms 构建 / 57MB 峰值 RSS | 源 ≥100MB 或 RSS ≥500MB(`design.md` §四 既有触发;本轮修正其措辞见 C4) |
304
+ | `append_handoff_turns` 全量 parse | 12ms/次(与 §四 既有记录一致) | 维持不修 |
305
+ | 每轮 3×`ls-tree` / 2–3×`ls-files` 重复读 | ~3ms/轮(0.1% 单核) | 子进程数/轮成为可观测瓶颈时(当前 8 个);不做收编(状态传递论证) |
306
+ | `--wait` 双读 | 每轮两次 `aggregate_mode` 全量读(incremental 一次兼作显示) | 登记进 F1 修复 commit;不修(两个替代法更差) |
307
+ | 三处冗余 pass(`_entry_text` 双算等) | 合计 ~11ms = 构建的 6% | 构建 >1s 时复测(首唤日志已含构建耗时与峰值 RSS) |
308
+
309
+ ---
310
+
311
+ ## 五、方法论沉淀(本讨论产出)
312
+
313
+ 1. **引用前先核对原文**:铁律视角把己方推演写成"某某/000N 的修正"——引用完整性要求
314
+ 包含"引用前核对"这一半(被引用物变化 → 同批更新全部引用处)。
315
+ 2. **超窗/守卫类修复的"三方独立探针"模式**:同一现象由三视角各自用不同方法观测
316
+ (性能测成本、铁律测机制、简单测调用面),结论互证后才定案。
317
+ 3. **修复完成判据 = 先红后绿测试 + 同步项**(不是"代码改完")。
318
+ 4. **共识 ≠ 用户裁决**:视角共识是**建议的修复清单**,实施取舍以用户确认为准。
319
+
320
+ ---
321
+
322
+ ## 六、未覆盖与开放项(诚实声明)
323
+
324
+ - **352 个测试未逐一审计**(抽样覆盖:fs 不变量、human 通道、main 路径、wrapper);
325
+ 断言强度的全量评估不在本轮结论内。
326
+ - **修复后回归未验证**:批 A/B/C 全部未实施(代码未改)——各自视角的"修复后回归"声明
327
+ 延续待实施后验证。
328
+ - **pi 端到端行为**:ceiling 注入后 pi 的 footer 等表现需真实唤醒回归确认。
329
+ - **pi 进程启动/上下文加载延迟**(唤醒延迟的一部分,pi 侧)未测。
330
+ - **批 A/B/C 的实施顺序与工作量**未评估(属实施计划)。
331
+ - 修复依据的性能数字均为本机、fixture 或活现场口径,真实会话正文分布不同时量级可能上浮
332
+ (离可感知阈值有两个数量级余量)。
333
+
334
+ ---
335
+
336
+ ## 七、给实施方的建议顺序
337
+
338
+ 1. **批 A**(守卫):独立小批,diff 小、收益明确(fail-closed + 1000× fail-fast);
339
+ 2. **批 B**(读取路径净删):涉及 4 文件,**建议单独立项评审**后再动手;实施时用 grep
340
+ 重新枚举调用点(不沿用任一视角的列表),两个契约点(失败语义 / 数据源)为前置条件;
341
+ 3. **批 C**(注释/文档):随 A/B 实施同批完成(同步项 = 完成判据的一部分)。
342
+
343
+ ---
344
+
345
+ *本结论由三视角协同分析产出;各方结论原文见 `work-性能/`、`work-简单/`、`work-铁律/`
346
+ 各消息文件(讨论目录 repo.git 历史为权威单一事实源)。*
@@ -0,0 +1,284 @@
1
+ > **存档说明**:2026-09-11 真实多视角自审(性能 / 简单 / 铁律)的 result.md
2
+ > 原文,主题为"review 本项目的代码,查看代码的合理性"。该轮抓到 8 项问题
3
+ > (R1–R8:校验多实现、wrapper 越界检查、`--start` 静默删 spec、session 查找
4
+ > 两套规则等)+ 6 项归并建议。修复见后续 commit;报告中的编号为**当次讨论
5
+ > 内部编号**,不可跨文档核验;同时该轮本身也是一次时间流实测(框架空闲仅
6
+ > 3 秒 / 44 分钟讨论)。
7
+
8
+ # 多视角分析结论:review 本项目的代码,查看代码的合理性
9
+
10
+ ## 0. 元信息
11
+
12
+ | 项 | 值 |
13
+ |---|---|
14
+ | 分析对象 | `/root/pi-multi-viewers`(约 4700 行 Python + wrapper),工作区 HEAD `663f013` |
15
+ | 参与者 | 性能 / 简单 / 铁律(3 视角,meeting 10/7 → RR 全员 pass) |
16
+ | 模型 | `opencode-go/deepseek-flash`(thinking max),fork 模式 `budget`(默认,携带主会话上下文) |
17
+ | 消息规模 | 性能 13 / 简单 10 / 铁律 11 条实质消息(共 34,另 2 条 loop 协议信号) |
18
+ | 任务边界 | 审阅——只提意见不改代码;只读命令 + 秒级探针(未跑全量测试套件) |
19
+
20
+ ## 1. 总体判断
21
+
22
+ 代码总体健康:分层清晰(core 纯判定 / fs I/O / engine 状态机 / loop 壳),单一事实源
23
+ 在近期修复后基本落实(protocol 单读原语、`check_agent_name` 单实现、`run_git` 读路径单点)。
24
+ 本轮发现集中在三类:**同一语义多份实现/规则分叉**、**制品文本(注释/文案)与机制保证不符**、
25
+ **边界越界(产品层替第三方扩展做体检、观测层绑定状态机常量)**。无高危缺陷
26
+ (无数据损坏、无流程死锁、无安全类问题);共 8 项确认问题 + 6 项归并 + 3 项性能记录项。
27
+
28
+ ## 2. 确认问题与处置
29
+
30
+ ### 2.1【中】R1 + R8:viewers/agents 校验与列举策略多实现,且 `--agents` 在 prepare 路径规则缺位
31
+
32
+ **R1(策略双实现 + 已漂移)**
33
+
34
+ - 同一套规则(目录缺失 / 空视角 / 名字合法 / ≥2)在两条路径各写一遍:
35
+ `_snapshot_viewers`(prepare,`start_discussion.py:533-571`)与 `_resolve_spec` 的
36
+ viewers 回退分支(`:622-657`)。
37
+ - 已漂移的证据(grep/读码核对):非法名文案 `:558`(带 `(viewers/{n}.md)`)vs
38
+ `:468`(不带);≥2 检查 `:560` vs `:653`(后者多列参与者)。`validate_participants`
39
+ docstring 自称「唯一文案」(`:464`),被 `:558` 自身证伪。
40
+ - 隐藏文件规则分叉:`.md` 列举全仓 3 处——`_discover_viewers:493`
41
+ (`and not f.startswith(".")`)、`_resolve_spec` 的 `.order` extra `:627` 与无 `.order`
42
+ 回退 `:631`(两处只判 `.md`)。后果:`spec/agents/.draft.md` → 参与者名 `.draft`
43
+ (`check_agent_name` 不拦点号),静默变成参与者。
44
+
45
+ **R8(实测:规则缺位,不只是文案不一致)**
46
+
47
+ - `_parse_agents` 只调 `_check_reserved`(**只查 human**),完整规则集在 `--spec-gen`
48
+ 路径**没有**应用。两条探针实测(产物已清):
49
+ - `--agents "a/b"` → `gen_spec_skeleton:297` **FileNotFoundError traceback**,且
50
+ spec 目录**半成品落盘**(models/background/question/README 已写、无 agents/)
51
+ ——既非"明确报错",也违反"不合规零产物";
52
+ - `--agents "a b,c"` → **骨架生成成功**(非法名 `a b.md` 与 `.order` 落盘),
53
+ 直到 start 的 `main:1147` 才被拒绝——用户在中间还审阅了这个 spec。
54
+
55
+ **处置(两条合并为同一处修法)**
56
+
57
+ - 策略级单点:`_discover_viewers` = 列举 + 非空(隐藏排除只此一处);共享
58
+ `_viewer_set_error(names, empty)` 承载"空正文文案 + ≥2";名字规则继续走
59
+ `check_agent_name`/`validate_participants`(已是单实现,只收**调用点**)。
60
+ - `_parse_agents`(调用点 `main:995`,早于 `--spec-gen` 分派 `:1008`)改调
61
+ `validate_participants`,删 `_check_reserved`(`:472`,单调用点薄包装)。
62
+ - **prepare 路径前提**:`--spec-gen` 分支在 `main:1022` 即 return,早于中心闸门
63
+ `:1147`——所以 `_snapshot_viewers` 内部的名字校验**必须保留**(它承载 prepare 路径的
64
+ "不合规零产物");`_resolve_spec:647` 的内层调用在 start 路径冗余,可删。
65
+ - **验收**:四入口(CLI `--agents` 的 prepare/start、viewers 的 prepare/start、
66
+ spec/agents 的 start)**同一违规 ⇒ 同一消息**,且**在任何文件落盘之前拦截**。
67
+ - 附带:`validate_participants` docstring 的「唯一文案」随本次修法成真或删除。
68
+ - 边界(不主张):写盘 I/O 失败(磁盘满/权限)下的半成品**不加**事务/回滚机制
69
+ ——复杂度不匹配,触发条件 = 真实出现。
70
+
71
+ ### 2.2【中】R2:wrapper 越界检查第三方扩展(AFT)配置
72
+
73
+ - `scripts/mv.sh:51-126 check_aft_bash()`(76 行 ≈ wrapper 的 20%,含内嵌 Python 的
74
+ 字符串感知 JSONC 解析器),在 `--prepare`(`:204`)/`--start`(`:272`)前读
75
+ `~/.config/cortexkit/aft.jsonc` 检查 `"bash": false`。
76
+ - 三个具体问题:
77
+ 1. **未装 AFT 的机器也告警**(配置不存在 → `_aft_warn`,`mv.sh:65`),且该行为被
78
+ `tests/test_wrapper.py::test_missing_config_warns` 固化为规格——未装 = 未接管 =
79
+ 一切正常,是假阳性,修复指引指向不存在的文件;
80
+ 2. 告警称「插话扩展找不到分析目录」——`/multi-viewers-say` extension **尚不存在**
81
+ (`package.json` 仅 `pi.prompts`,无 `extensions/`),该后果当前不可发生;
82
+ 3. README/AGENTS/design 均未记录这条环境要求。
83
+ - 边界判据:AFT 是另一个 pi 扩展,其配置属**运行环境**;本工具 wrapper 的职责是
84
+ prepare/start/view/say/cleanup。且"models.md 退化"的真正弱项是自家的 R6
85
+ ——**告警让用户去修别人的配置,而弱项在我们仓内**。
86
+ - **处置**:删除为主;备选(保留告警)= 仅在 AFT 配置存在时检查 + 后果文案改为真实后果
87
+ + README 一行环境要求。
88
+
89
+ ### 2.3【中】R3:`--start` 静默删除 spec 目录(可审计性)
90
+
91
+ - `scripts/mv.sh:301` `rm -rf "$spec_dir"`:无守卫(只要求 `question.md` 存在)、无提示、
92
+ 无文档;而 prompt 明确让用户"查看/编辑 spec"(`prompts/multi-viewers.md` 步骤 2)。
93
+ - 实测当前讨论目录:bare HEAD 只跟踪 `.gitignore`/`protocol.json`/`question.md`/消息文件
94
+ (核对时点 13 条);`AGENTS.md`、`.pi/`、`pi-agent.json` 被 `.gitignore` 排除(本地文件)。
95
+ 推论:`--start` 删 spec + `--cleanup` 删讨论目录 ⇒ **视角任务书(agents 快照)、注入
96
+ 背景(AGENTS.md 背景节)、models 配置在 cleanup 后不可恢复**——spec 是这些内容在用户侧
97
+ 的**唯一副本**。
98
+ - **处置**:
99
+ - 必须级(零成本、独立成立):删除前打印一行 + **只删本工具生成形态**(`mv-spec-*`);
100
+ - 可选级(需裁决):要审计链则选"**不删**"(零机制优于新增"归档"机制),README 说明
101
+ `mv-spec-*` 由用户处置;**不要**在 `--cleanup` 新增"猜 spec 路径"机制(新耦合)。
102
+
103
+ ### 2.4【低】R4:`main` 233 行 / `agent_loop` 231 行(补丁沉积观察)
104
+
105
+ - `start_discussion.main`(`:956-1188`)= argparse 声明 + 5 个命令分支 + setup/start 全混;
106
+ `meeting_engine.agent_loop`(`:526-757`)分支 ①–⑤.5,注释含约 15 处历史修复溯源。
107
+ 项目自己的方法论把 `main()`/CLI 分发列为独立测试盲区(拼接点教训)。
108
+ - **处置**:`main` 支持按命令机械拆分,**优先抽 `--wait` 内联循环**(≈`:1046-1101`,
109
+ main 里最大单块);`agent_loop` 只做小步归并(见 2.8 ④),不做整体重排。
110
+ - **归并护栏(一条可验收不变量)**:**纯结构变换——调用序列与 sleep 序列逐字不变;
111
+ 主流程阶段骨架的可见性不降。** 具体化:helper 只做机械动作、不吸收"何时进入分支"的
112
+ 阶段判断;不增加 bare 读取次数;轮内快照的派生关系保持;两侧日志可区分。
113
+
114
+ ### 2.5【低】R5:`check-residue.sh` 注释"同一判据"失真
115
+
116
+ - `scripts/check-residue.sh:104` 称"与 `start_discussion._loop_pids` 同一判据";实际
117
+ bash 匹配 `*/meeting_loop.py`(**任意**讨论的 loop,残留检查语义),python `_loop_pids`
118
+ 匹配 `== <base>/meeting_loop.py`(**指定**讨论,存活语义)——同一**技术**(argv 逐项精确
119
+ 匹配),**判据不同**。属"声明 ≠ 保证"类。**处置**:改注释为"同一技术、判据不同"。
120
+
121
+ ### 2.6【低】R6:"当前 session 文件"两套查找规则(+ 记录项)
122
+
123
+ - `resolve_fork_source`(`:507-530`)按 `PI_SESSION_ID` 匹配文件名;
124
+ `_detect_pi_model_thinking` 兜底(`:124-130`)取 sessions 目录内**字典序最后**的
125
+ `.jsonl`——同一概念两套选择规则,可能选到别的 session(低危:只影响 models.md 预填)。
126
+ - **处置**:共用 `current_session_file()` 原语(sid 优先,兜底同一规则)。
127
+ - **记录项(尾部反向扫描,不催本轮)**:兜底现为整文件逐行解析(~0.1s,仅 prepare 且
128
+ env 缺失时触发)。若改尾部扫描,验收条件:
129
+ 1. **停止条件与穷尽条件分离**——两类事件都找到才停;到达文件头是**合法常路**(事件可能
130
+ 根本不存在,如只有 `model_change`),"找不到"正常返回,不得抛错/重试/死循环;
131
+ 2. 语义与现值**逐位一致**:最后一次出现胜出;
132
+ 3. **不得设"有界前缀"**——找不到就继续向头部扫(有界截断 = 静默降级到 `_default_model()`);
133
+ 4. 实现细节:反向块读要丢弃/拼接半行,且块须**扩张直至覆盖整行**(JSONL 单行可能超长);
134
+ 5. 等价测试:以**现全扫实现为测试期 oracle**(先例:`tests/test_meeting_fs.py` 的
135
+ `_replay_visible`——"测试期 oracle + 禁生产复刻"),fixture 覆盖事件在头/尾、仅单类
136
+ 事件、两类都无、**事件跨块边界**;等价测试只钉**正确性**(返回值逐位相等),
137
+ **性能属性用测量、不用断言**。
138
+
139
+ ### 2.7【低】R7:切换叙事拼接 agent 定义文件正文
140
+
141
+ - `meeting_loop._prepare_fork_session`(`:280-293`)读 `.pi/agent/<agent>.md`
142
+ (`gen_agent_def` 产物,含生成头「# Pi 多视角参与者 X / 你是 X… / 参与者:… / 你使用
143
+ 模型…」)整段拼进 user turn;`_read_perspective_brief`(`:452-474`)**>500 字截断**在
144
+ 句中。身份/任务措辞因此在三处各自生成(`gen_agent_def` / 切换叙事 / `AGENTS.md.tpl`)。
145
+ - **处置**:切换叙事只写「身份与视角任务书见 system prompt 注入;主题:X」,不拼文件正文
146
+ (消第三处身份措辞与 500 字截断分支),与 viewers 分层方向一致。
147
+
148
+ ### 2.8 简单视角的六项归并/删除(性能评估全部中性,逐项处置)
149
+
150
+ 1. **轮转表达式 ×4**(`meeting_engine.py:356/:420/:635/:675`)+
151
+ `order = agents` 别名 ×2(`:355/:537`)→ 收成 `_next_in_order(order, agent)`
152
+ (纯表达式、无 I/O;放 engine 私有或入 core 便于单测)。
153
+ 2. **`bare` 推导 ×13**(engine `:63/:74/:549/:555`、loop `:383/:549`、fs `:938`、
154
+ viewer `:150`、sayer `:48/:117`、start `:908/:1055/:1110`)→ fs 单点。
155
+ **形态约束**:不得"接受 workdir 或 base"(那是按值形状猜入参,违反 `_join_model_ref`
156
+ 已立的禁止类)→ 两个具名入口,或调用方规范化后统一传入。判定类 API 统一收 bare 时
157
+ **必须删掉** engine 的"函数体内自推 bare"兼容壳(否则留下第三种约定);
158
+ `human_viewer.participants_from_bare` 的重复随之消解。
159
+ 3. **两个纯转发壳**:`meeting_engine._cat_batch`(→ `meeting_fs.cat_batch`)、
160
+ `meeting_loop._unlock_git`(→ `restore_git_lock`)——删壳直调(批读形态/锁语义不变),
161
+ 测试引用同步(`tests/test_meeting_engine.py` 导入 `_cat_batch`、
162
+ `tests/test_meeting_loop.py:630` 调 `_unlock_git`)。删壳前确认二进制读的理由
163
+ (cat-file size 是字节数 vs `read(size)` 是字符数 → 中文错位死锁)完整保存在
164
+ `meeting_fs.cat_batch` docstring。
165
+ 4. **`agent_loop` 两组结构重复** → 小步归并:
166
+ - RR 表态块 ×2(starter `:623-638` / 轮次 `:664-682`):helper 边界止于
167
+ "`read_point` → `new_messages_with_meta` → 分支(respond | 写 pass)";
168
+ **"不是我的回合 → sleep" 留在调用点、不进助手**;两侧日志走显式参数区分。
169
+ - "rw 收尾 / 其余等待" ×2(consensus `:647-653` / quota `:656-663`):原样移入
170
+ `_finalize_or_wait(...)`,**每条路径恰一次 sleep 或一次 finalize**,`continue`
171
+ 语义与顺序不变。
172
+ 5. **`result.md` 实际路径 ×2**(`human_viewer.result_path:42-45`、
173
+ `start_discussion.py:1090`)→ fs 单点或共享实现(同一次 `read_protocol` 等量替换,
174
+ 不改变调用次数)。
175
+ 6. **测试环境骨架 ×6+ 文件**(`test_meeting_engine.make_env`、`test_viewer.make_discussion`、
176
+ `test_flow_composition`/`test_main_paths`/`test_dir_forms` 的 setUp、`test_human._env`、
177
+ `test_stage4`/`test_meeting_concurrency` 同型)→ `tests/helpers.py` 的
178
+ `make_discussion(participants, rw, **extra)`;各文件保留特化构造(测试独立性不牺牲),
179
+ helper 不得在用例间共享可变状态。
180
+
181
+ ## 3. 性能视角:记录项(不修 + 触发条件)与等待点核验
182
+
183
+ ### 3.1 构建期(一次性;已量化;不修)
184
+
185
+ | 项 | 实测 | 触发条件 / 观测点 |
186
+ |---|---|---|
187
+ | `json.loads` 占构建耗时 **49%**(15.3MB 源 → 181ms/agent) | 本机实测 | 构建 >1s(观测点=首唤日志"构建 Nms/峰值 RSS") |
188
+ | `entries`/`folded` 双列表驻留 → RSS 峰值 **55MB ≈3.6×** 源 | 本机实测 | 峰值 RSS >~500MB 或源 >~100MB |
189
+ | 三 agent 各构建一次(≈543ms) | 本机实测 | N≫3 且会话至 100MB 量级 |
190
+ | 三处冗余 pass ≈11ms(构建的 6%) | 本机实测 | 构建 >1s |
191
+
192
+ 第一优化点是**流式解析**而非折叠算法(49% 在 `json.loads`),但会扩大"先折叠再裁"的
193
+ 证明面——未到触发条件不动;"共享 budget 基座"已评估否决(跨进程失效/原子写义务 > 收益)。
194
+ **反对线**:① 把循环顶批量读拆回逐条 `git_show` = **16.8× 回归**(实测);② 为省
195
+ 37MB/进程把构建改流式(证明面扩大、未到触发条件)。
196
+
197
+ ### 3.2 热路径(多轮)
198
+
199
+ 每轮循环顶一次全量 bare 批量读(消息类判定由此派生)+ 常数次无状态读
200
+ (`human_msg_count` / `_stall_elapsed` / RR `rr_next_speaker`)≈ **26.5ms/轮**
201
+ (3 agent 并发 ≈4% 单核)。**不为省 ~3ms/轮** 引入跨函数快照(会把状态耦合进判定签名);
202
+ **不引入每轮缓存**(协议读取缓存成进程状态 = 把"protocol 不再变化"变成未成文的隐式不变量,
203
+ 且与"判定基于最新共享事实"的修复方向相悖)。
204
+
205
+ ### 3.3 等待点核验
206
+
207
+ - `human_viewer --follow`:`POLL_INTERVAL = 2.0s` → 观看滞后 ≤2s,**无问题**;
208
+ - `start_discussion.py:1100`:`--wait` 硬编码 `time.sleep(10)` → 结束观察延迟最长 10s,
209
+ 是唯一命中">10s 非必要等待"的点 → **统一为 ≤2s**(见 §4)。
210
+ - 未覆盖:`finalize_discussion`(rw 收尾的一次 LLM 调用)耗时占比未测——建议收尾时纳入
211
+ 基线,区分"LLM 生成"与"流程间隙"。
212
+
213
+ ## 4. 唯一分歧及其关闭:`--wait` 常量归属
214
+
215
+ - **值**三方一致:观察延迟 10s → ≤2s(每次 `check_status` ≈3 个 git 子进程 + /proc 扫描
216
+ ≈1.1ms,2s 频率 → 观察者 CPU <2% 单核;5× 频率换掉 8s 观察延迟,划算)。
217
+ - **符号归属**(曾分歧,已关闭):铁律主张**消费端常量**——`POLL_INTERVAL` 是状态机
218
+ 空闲重试节奏(与 API/CPU 成本相关),观察刷新节奏是另一个概念(与 UX 延迟相关);
219
+ 用 import 绑定会让"调 loop 节奏"静默改变观察契约(动作-远距离耦合)——
220
+ **共享值 ≠ 共享概念**。性能 `0005` 撤回绑定、接受消费端常量;简单 `0004`/`0005` 接受。
221
+ - **结论**:定义 `human_viewer.OBSERVER_POLL_INTERVAL = 2.0`,viewer 与 `--wait` 共用;
222
+ 注释写明「与 loop 节奏当前相等但**有意独立**」+ 下界 **≥1s**(防将来有人调到 0.1s
223
+ 变成 ~30% 单核的无谓开销)。
224
+
225
+ ## 5. 归并护栏的计数断言(新增测试规格)
226
+
227
+ - **前提**:只能在"注入 responder、进程内跑 `agent_loop`"的测试里做(先例:
228
+ `tests/test_human.py:360-372` 的线程形态);走 `spawn_agents` 的多进程形态父进程
229
+ **计不到**。
230
+ - **双入口**:`meeting_fs.run_git`(`subprocess.run`,`meeting_fs.py:34`)与 `cat_batch`
231
+ (`subprocess.Popen`,`:230`)——**只计 `run_git` 会漏掉热路径的批量读本身**。
232
+ - **替身形态(两案)**:`mock.patch.object(meeting_fs.subprocess, "run"/"Popen", …)`
233
+ 打的是 **subprocess 模块对象本身**(进程全局,隔离来自 test window);若要 fs 级隔离,
234
+ 需替换 `meeting_fs.subprocess` 模块属性(此时才面对透明代理问题:须透传
235
+ `DEVNULL`/`PIPE` 等常量)。**两者皆可,但声明的隔离性要写对**。
236
+ - **分类**:按 git 子命令区分读/写(读类集合写成一个测试模块级常量),**只对读类断上界**
237
+ ——in-process 形态下测试自身也在经 fs 写消息,不分类会把上界污染。
238
+ - **形态**:scenario 级(N 轮 FakeAgent 的总上界),**不在生产层加计数钩子**(测试关注点
239
+ 不进产品代码);计数器用 `list.append` + 末尾 `len`。
240
+ - **上界须有推导**(实测值 + 余量写进测试 docstring)——无推导的常数是任意数,失配时会被
241
+ 随手放宽。
242
+ - **寿命**:断言对象是**结构**(每轮 spawn 上界),不是速度;"每轮一次全量读 + 常数次
243
+ 无状态读"是**既定设计决策**,故**结构性断言随设计走**(长于本次归并),
244
+ **速度测量永不进单测**。
245
+
246
+ ## 6. 确认无问题(有证据)
247
+
248
+ - `meeting_core.py` 纯判定:无 `import os/json/subprocess`(grep 验证)——core 不沾 I/O;
249
+ - engine 侧 I/O 已归 fs 原语(`file_size`/`write_text`/`remove_message`/`cat_batch`),
250
+ 无裸 `open(w)`/`os.remove`;
251
+ - protocol 读取单源:engine / loop / status / viewer 均经 `meeting_fs.read_protocol(bare)`,
252
+ 无第二实现;`check_status` 的 concluded 判定复用 `aggregate_mode`(非 `git grep` 全文);
253
+ - git **读**路径统一 `fs.run_git`(quotepath 加固单点);`run_cmd` 仅用于一次性环境命令;
254
+ - **A1 守卫现场证据**:本次讨论唤醒期间 `work-铁律/.git.locked` 存在,在 workdir 内执行
255
+ git 得到 "not a git repository" 而非上溯主仓库——锁 + `GIT_CEILING_DIRECTORIES` 在真实
256
+ 运行中 fail-closed;
257
+ - wrapper 无 python 逻辑复刻(status/wait/prepare 全转发;`--agents` 解析、viewers 校验、
258
+ fork 源解析均在 python)。
259
+
260
+ ## 7. 未覆盖(诚实边界)
261
+
262
+ - `tests/` 362 个测试的断言强度未逐条审(建议:R1 修法后为**两条路径各**构造用例验证
263
+ "同违规同消息");
264
+ - `docs/` 与实现的全量对照未做(只抽查 design.md 协议/规模口径相关节);
265
+ - 性能:git fetch/push 延迟、human 通道 push 容错重试耗时、pi 冷启动固定开销、
266
+ `finalize_discussion` 耗时未测;
267
+ - `scripts/mv.sh` 除 R2/R3 相关区域外的其余行未逐行审。
268
+
269
+ ## 8. 过程与纪律说明
270
+
271
+ - **阶段**:meeting(多轮交锋,含两处实测探针)→ 全员 freezing(含 2 条 loop 协议信号)
272
+ → RR 轮转全员 pass(性能 → 简单 → 铁律)→ 收尾。
273
+ - **证据纪律**:所有引用落到 `文件:行`;实测探针两处(R8:`--agents "a/b"` 崩溃与
274
+ 半成品落盘、`--agents "a b,c"` 骨架成功;R6:4 个真实 session 中 model/thinking 事件
275
+ 位置抽查——最后一次落在 94–100% 文件位置)。
276
+ - **"声明 ≠ 保证"类目**(两处):`validate_participants` 的「唯一文案」、`check-residue.sh`
277
+ 的「同一判据」——均以证据推翻并给修法。
278
+ - **自我修正**:铁律视角原假设"model/thinking 事件集中在文件头,故尾部扫描无益"被实测
279
+ 数据推翻(事件贯穿全文、最后一次在 94–100%),当场撤回隐含反对。
280
+ - **术语校正**:`简单/0010` pass 中写的 `_available_agents` 实为 `_parse_agents`
281
+ (全仓无前者)——实施时以 `_parse_agents` 为准;`validate_participants` 中心闸门
282
+ 位于 `start_discussion.py:1147`。
283
+ - **共识达成**:34 条消息全部收敛,无遗留异议(唯一曾分歧的 `--wait` 常量归属已按
284
+ 消费端常量关闭)。