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.
- package/AGENTS.md +192 -0
- package/README.md +153 -0
- package/docs/design.md +288 -0
- package/docs/examples/first-experiment/README.md +42 -0
- package/docs/examples/first-experiment/work-a/AGENTS.md +10 -0
- package/docs/examples/first-experiment/work-a/a/0001.md +65 -0
- package/docs/examples/first-experiment/work-a/a/0002.md +70 -0
- package/docs/examples/first-experiment/work-a/perspective.md +5 -0
- package/docs/examples/first-experiment/work-b/AGENTS.md +10 -0
- package/docs/examples/first-experiment/work-b/b/0001.md +100 -0
- package/docs/examples/first-experiment/work-b/perspective.md +6 -0
- package/docs/reviews/2026-09-10-e2e10-fork-source-modes-review.md +366 -0
- package/docs/reviews/2026-09-10-e2e11-forkmode-guards-review.md +229 -0
- package/docs/reviews/2026-09-10-e2e12-code-review.md +346 -0
- package/docs/reviews/2026-09-11-e2e13-code-review.md +284 -0
- package/docs/reviews/2026-09-11-e2e14-observability-review.md +216 -0
- package/docs/reviews/README.md +36 -0
- package/docs/test-methodology.md +258 -0
- package/extensions/multi-viewers-say/index.ts +156 -0
- package/fake_agent.py +120 -0
- package/human_sayer.py +144 -0
- package/human_viewer.py +215 -0
- package/meeting_core.py +255 -0
- package/meeting_engine.py +733 -0
- package/meeting_fs.py +1066 -0
- package/meeting_loop.py +606 -0
- package/package.json +41 -0
- package/prompts/multi-viewers.md +94 -0
- package/scripts/check-residue.sh +190 -0
- package/scripts/mv.sh +325 -0
- package/start_discussion.py +1485 -0
- package/templates/AGENTS.md.tpl +100 -0
- package/templates/agent.md.tpl +9 -0
- package/templates/gitignore.tpl +7 -0
- package/templates/spec-readme.md.tpl +87 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
> **存档说明**:2026-09-11 真实多视角分析(性能 / 简单 / 铁律)的 result.md
|
|
2
|
+
> 原文,主题为"如何更好的监控整个 discussion 过程的有效性和可监控性?
|
|
3
|
+
> 目前的日志系统是否合理?"——即**对本项目自身可观测性的自审**。
|
|
4
|
+
> 抓到 11 项问题(P1–P11)与 5 条判据纪律,修复见后续 commit。
|
|
5
|
+
> 本轮同时是 `/multi-viewers-say` extension + `mv-*` 目录前缀的**首次真实
|
|
6
|
+
> 端到端验证**(human 插话"目前的日志是否有时间戳?"被各视角实质采纳,
|
|
7
|
+
> 结论 §3.5 落成"时间戳升级为 ISO8601")。报告编号为**当次讨论内部编号**,
|
|
8
|
+
> 不可跨文档核验;引用的行号与实测数字均对应当时工作区状态。
|
|
9
|
+
|
|
10
|
+
# 如何更好的监控整个 discussion 过程的有效性和可监控性?目前的日志系统是否合理?
|
|
11
|
+
|
|
12
|
+
- 分析日期:2026-09-11
|
|
13
|
+
- 参与者:性能 / 简单 / 铁律(3 视角,三轮 me + RR 收束)
|
|
14
|
+
- 任务性质:**审阅 + 改进建议,不修改代码**;全部建议未经实施与测试
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 0. 结论摘要
|
|
19
|
+
|
|
20
|
+
**"日志系统是否合理"的直答**:
|
|
21
|
+
|
|
22
|
+
- 作为**人类排错工具**:**合理**——分层清楚(loop log / wake-logs / status json 各司其职)、单点落盘、成本可忽略(3.4–4.0KB/唤醒)、且是**纯信息层**(全仓无一处解析日志内容参与流程判定)。
|
|
23
|
+
- 作为**度量工具**:**不成立**——耗时 / token / 失败重试这几类关键信号要么没有落点,要么只能事后重扫 MB 级数据;一次 44 分钟讨论中**最大的单项浪费(provider 失败重试 ≈11% 墙钟)恰好是最大的观测盲区**(零记录)。
|
|
24
|
+
|
|
25
|
+
**修正方向**(三方共识):不是把日志升级为度量接口,而是四件事——
|
|
26
|
+
|
|
27
|
+
1. **契约显式化**:给每个观测面立一节契约(现在格式/消费者在文档零命中);
|
|
28
|
+
2. **登记两个"无家可归"的字段**(`elapsed_ms` / `rc`,零解析热路径);
|
|
29
|
+
3. **新增唯一只读报告出口 `--report`**(冷路径,读 bare + 登记字段 + session 文档化字段);
|
|
30
|
+
4. **修掉唯一一处"日志兼任判定输入"**(`--wait` 的 `glob(loop-*.log)` 分支,删除)。
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 1. 现状盘点(讨论中核实过的事实)
|
|
35
|
+
|
|
36
|
+
| 观测面 | 写者 | 格式 | 消费者 | 成本 |
|
|
37
|
+
|---|---|---|---|---|
|
|
38
|
+
| `loop-<agent>.log` | loop + engine(stdout 重定向) | `[HH:MM:SS] agent: msg` 自由文本 | 人(grep/肉眼) | 2–5 行/唤醒 |
|
|
39
|
+
| `wake-logs/<agent>-<epoch>.txt` | loop | CMD + PROMPT 全文(**PROMPT 段与 CMD 内嵌 prompt 逐字重复,占文件 40–43%**) | 人(排错第一手段) | 3.4–4.0KB/唤醒 |
|
|
40
|
+
| `status-<agent>.json` | loop | `{"sessionID": ...}` | 流程读取(崩溃恢复) | 一行 |
|
|
41
|
+
| `pi-sessions/fork-src-*.jsonl` | pi | 完整 entry(ISO8601 时间戳、`usage`、`stopReason`) | 现状仅 fork 构建 / 会话头解析 | **O(会话总量),MB 级且随轮次增长** |
|
|
42
|
+
| `result.md`(固定位) | resultWriter loop | 结论文档 | 人 | — |
|
|
43
|
+
| viewer | —(只读派生) | 状态 + 消息增量 | 人 | 每 2s ≈4 个 git 子进程 + O(全部消息) 解析 |
|
|
44
|
+
|
|
45
|
+
**关键事实**:
|
|
46
|
+
|
|
47
|
+
- **日志是纯信息层**:全仓无一处解析日志**内容**做判定(唯 `glob(loop-*.log)` 存在性做过显示分叉——本轮裁定删除,见 §3.4)。
|
|
48
|
+
- **契约是隐式的**:四个观测面的格式/消费者在 `design.md` / `README.md` **grep 零命中**——"日志有没有时间戳"需读源码才能回答,这本身就是症状。
|
|
49
|
+
- **e2e13 实测(同日本项目的 44 分钟真实讨论)**:框架空闲仅 **3 秒(0.1%)**;**5 次 provider 失败重试 ≈ 11% 墙钟,loop log 零记录**;log 行无日期、无毫秒、无 duration。
|
|
50
|
+
- **契约 vs 设计约定**:`design.md` §77"预计值不得用作验收口径:日志中的派生数字只是可读性便利"——约束的是**估算/派生数字不得充当验收口径**,不是"日志不得被消费"(讨论中校正了过宽读法)。
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 2. 确认的问题清单(按严重度)
|
|
55
|
+
|
|
56
|
+
**高**
|
|
57
|
+
|
|
58
|
+
| # | 问题 | 证据 |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| P1 | **失败/重试不可见**:wake 错误路径只覆盖 rc≠0 且 stderr 匹配 "No session found" 与超时异常;provider 错误(`stopReason=error`、rc=0)完全静默 | e2e13:5 次失败 ≈11% 墙钟零记录 |
|
|
61
|
+
| P2 | **耗时不可度量**:秒级、无日期、无 duration;跨天/多 loop 合并时间线不可判 | log 行仅 `HH:MM:SS` |
|
|
62
|
+
| P3 | **token/成本不可见**:无任何聚合出口 | usage 只在 session JSONL 里 |
|
|
63
|
+
|
|
64
|
+
**中**
|
|
65
|
+
|
|
66
|
+
| # | 问题 | 证据 |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| P4 | **日志兼任判定输入(唯一实例)**:`--wait` 用 `glob(loop-*.log)` 存在性区分"已启动崩溃/尚未启动" | start_discussion.py:1024 |
|
|
69
|
+
| P5 | **状态词表/判据分叉**:viewer done = `mode==concluded`;`check_status` done 额外要求 result.md 有效 → viewer 会在 result.md 落盘前先报"已结束",且打印尚不存在的路径 | `incremental` vs `check_status` |
|
|
70
|
+
| P6 | **"有效性"三样观测面无出口**:冻结集合与顺序 / 配额进度(meeting x/10、RR y/7)/ RR 轮转位置——**事实全在 bare(纯派生),但无任何出口** | viewer grep 配额/冻结 = 0 命中 |
|
|
71
|
+
| P7 | **契约隐式化**:观测面的写者/格式/消费者/成本档无文档 | design.md 零命中 |
|
|
72
|
+
|
|
73
|
+
**低**
|
|
74
|
+
|
|
75
|
+
| # | 问题 | 证据 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| P8 | wake-log prompt 双写(同一内容两个位置) | 实测逐字节重复,40–43% |
|
|
78
|
+
| P9 | 两份 `log()` 实现(loop / engine 逐字相同)——格式契约复制两份 | meeting_loop.py:71、meeting_engine.py:50 |
|
|
79
|
+
| P10 | 同一"时间"三种表示:log 秒级无日期 / wake-logs 文件名 epoch / commit 时间 | — |
|
|
80
|
+
| P11 | viewer 轮询是唯一"O(全量)×轮询"形态(<1% 单核,非瓶颈,但随消息数增长) | 每 2s ≈4 git 子进程 + 全量消息解析 |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 3. 共识结论:该做什么(最小集)
|
|
85
|
+
|
|
86
|
+
### 3.1 观测面契约节入 `design.md`(第一批,零代码)
|
|
87
|
+
|
|
88
|
+
每面登记:**owner / 格式 / 消费者 / 是否参与判定 / fail-open 性质 / 缺省语义 / 取数成本档 / 人类专用面**。配套写死三条**不变量与纪律**:
|
|
89
|
+
|
|
90
|
+
- **日志零判定输入(绝对)**;唯一机器内容消费 = `--report` 读取登记字段(fail-open)。
|
|
91
|
+
- **缺席 ≠ 0**:观测拿不到的显示 `n/a`,绝不允许把"没测到"写成 0。
|
|
92
|
+
- **一个数字一个口径**:跨度/口径必须标名(wake 跨度 ≠ per-response 跨度 ≠ 墙钟)。
|
|
93
|
+
|
|
94
|
+
取数成本档(未来提案的准入闸门):
|
|
95
|
+
|
|
96
|
+
| 档 | 面 | 规则 |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| O(1) 捕获 | 数据已在手处(唤醒返回的进程返回值) | 鼓励;只落已登记字段 |
|
|
99
|
+
| O(小) 冷路径 | `--report`、`read_protocol` | 允许:按需一次 |
|
|
100
|
+
| O(MB) 全量解析 | `pi-sessions/*.jsonl` | **禁轮询/常驻**;仅冷路径按需 |
|
|
101
|
+
|
|
102
|
+
### 3.2 登记两个字段(第一批,零解析)
|
|
103
|
+
|
|
104
|
+
现有唤醒/完成行追加:
|
|
105
|
+
|
|
106
|
+
- **`elapsed_ms=<int>`**:monotonic 差值;**跨度 = pi 进程生命周期(spawn → exit)**;
|
|
107
|
+
- **`rc=<int>`**:**总是写**(零成本、权威);超时/被 kill 时不可得 → 省略(缺席 ≠ 0);
|
|
108
|
+
- 行尾追加;新字段增长过同一"家有无"闸门;ISO8601 时间戳一并升级(**人类可读性/定位用,非度量基础**)。
|
|
109
|
+
|
|
110
|
+
理由:这两个量**无家可归**(session 首个 entry 之前是黑箱;rc 只有 Popen 知道)——是"无家的就地捕获"、零解析、零 schema。`rc` 同时修掉 P1 的诊断面(一个字段满足"偏差③诊断"与"失败可见")。
|
|
111
|
+
|
|
112
|
+
### 3.3 新增 `--report`(第一批,唯一只读出口)
|
|
113
|
+
|
|
114
|
+
- **输入**:bare(流程时间线/推进节奏)+ 登记字段(`elapsed_ms`/`rc`,可选输出唤醒失败计数)+ **session 的文档化字段**(`usage` / `stopReason` / `timestamp`;流式预过滤;**单一适配器**)。
|
|
115
|
+
- **约束**:冷路径一次性;**不持久化**(视图不占"数字的家";将来审计需求走显式 opt-in);fail-open(读不出 → `n/a`);跨度分标;**不得升级为验收 gate**。
|
|
116
|
+
- **可输出的有效性代理**(全部派生/复用,无新增持久化):轮次与配额消耗、RR `pass` 比例、human 插话次数、stall 触发、**最长无进展间隔**(bare commit 间隔)、per-agent 耗时/失败/token。
|
|
117
|
+
|
|
118
|
+
### 3.4 删 `glob(loop-*.log)` 分支 + 修正 `--wait` 提示文案(第一批)
|
|
119
|
+
|
|
120
|
+
- 推演确认:现状两个分支的提示**都有可执行性问题**——`--start` 对已存在目录报"请先 --cleanup"(start_discussion.py:1167);wrapper `--start <base>` 因缺 question.md 失败;真正可执行的是 `--skip-setup --start`(环境不完整则 `--cleanup` 重建)。
|
|
121
|
+
- 合并文案(动作完整):`[wait] 未在运行且未收尾(无 loop 存活、无 result.md)——查 loop-*.log / status-*.json 判断原因;重跑:--skip-setup --start(protocol 缺失则先 --cleanup 后重建)`。
|
|
122
|
+
- 连带:`tests/test_main_paths.py` 两例随改。不变量随之成为**绝对式**(无例外条款)。
|
|
123
|
+
|
|
124
|
+
### 3.5 其余(第一批,小改)
|
|
125
|
+
|
|
126
|
+
- **wake-log 去 PROMPT 双写**(理由 = 单一来源,**不是**性能);顺手把 CMD 按 argv 逐元素 `shlex.quote` 成单行(wake-log 才可真行级 grep)。
|
|
127
|
+
- **`log()` 合并为单实现**,落 **`meeting_fs`**(IO owner;两模块已 import;与"engine IO 收归 fs"同向;调用点零改动)。契约句:**"文件由 loop 重定向拥有;loop 与 engine 各自产生自己的事件;格式只有一个实现"**。
|
|
128
|
+
- **"收尾完成"判据单源** = `concluded` **且** result.md 有效;合成函数落 `human_viewer`(唯一增量实现的持有者),复用同一次 bare 读取——同时修掉 P5。
|
|
129
|
+
|
|
130
|
+
### 3.6 第二批(不与本轮捆绑)
|
|
131
|
+
|
|
132
|
+
- **三样观测面**(冻结集合与顺序 / 配额进度 / RR 位置)判定**下沉为公开单一实现**(`_meeting_speak_count` 私有、quota 计算内联在 engine 循环内),出口 = `--report` 现场算;viewer 展示后置;
|
|
133
|
+
- 约束:**单次读取**(复用 viewer 已有的那次消息读取,不得新增 git 子进程);viewer 与 `--report` **共用一个实现**;
|
|
134
|
+
- viewer HEAD 短路**重估时点**随第二批(那时刷新成本上升,收益同步上升,同一笔账)。
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 4. 明确不做(及理由)
|
|
139
|
+
|
|
140
|
+
| 不做项 | 理由 |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `tok`/`err` 捕获进日志 | **有家**(session 文档化字段)→ 复用,不复制 |
|
|
143
|
+
| 第二持久化面(如 `events-<agent>.jsonl`) | 信息不减就不新增数据面 |
|
|
144
|
+
| 心跳文件 / 独立常驻监控进程 | 可从 `/proc` + HEAD 派生;第二事实源 → 双写/残留/一致性成本 |
|
|
145
|
+
| 轮询/常驻路径消费 MB 面 | 成本随轮次线性放大(`json.loads` 实测占 fork 构建 49%) |
|
|
146
|
+
| 运行期 LLM 评分(有效性裁判) | 不确定判定入流程,与"确定性归 loop"冲突;有效性判断留给人 + result.md |
|
|
147
|
+
| 目录内 retention / 轮转 | cleanup 是唯一清理点;目录内再加保留策略 = 新增机制 |
|
|
148
|
+
| message frontmatter 加时间戳 | 第二事实源 + **该字段由 LLM 写**(不可信);权威时间 = commit 时间 |
|
|
149
|
+
| 日志 JSON 化 / 引入日志库 | 主消费者是人(grep + 肉眼);机器可读用登记字段 |
|
|
150
|
+
| viewer HEAD 短路 | **暂缓**(收益 <1% vs 新分支 + 一条会随未来状态失效的前提;重估时点见 §3.6) |
|
|
151
|
+
| 报告自动落固定位 | 视图不占家;留存需求 → 显式 opt-in |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 5. 核心判据与纪律(本轮的智识产出)
|
|
156
|
+
|
|
157
|
+
### 5.1 取数判据"**家有无**"(三分支,替代整体二分)
|
|
158
|
+
|
|
159
|
+
> ① **家有无**:该量是否**已有一个结构化的家**?
|
|
160
|
+
> - **无家** → **登记**,给它造家(`elapsed_ms`、`rc`);
|
|
161
|
+
> - **有家且读它不需要越界假设**(文档化字段)→ **复用**(`tok`/`err`;冻结/配额/RR 从 bare 派生);
|
|
162
|
+
> - **有家但只能靠未文档化的私有细节读出** → 按**无家**处理(登记)。
|
|
163
|
+
>
|
|
164
|
+
> ② **非判定性**:零参与流程判定。
|
|
165
|
+
> ③ **可降级性**:删掉产物后行为/判定/复现不变;观察读点允许,但必须 fail-open。
|
|
166
|
+
> ④ **成本**:捕获在数据已在手处;产物入既有行或 KB 级;禁入轮询。
|
|
167
|
+
|
|
168
|
+
### 5.2 三域 owner(事实源边界)
|
|
169
|
+
|
|
170
|
+
| 域 | 事实 | 性质 | 取数 |
|
|
171
|
+
|---|---|---|---|
|
|
172
|
+
| bare(git) | 消息 / frontmatter / 协议 | **判定域** | 现场派生 |
|
|
173
|
+
| loop log | 进程事实(唤醒/退出/超时/rc) | 无家 | **捕获**(零解析) |
|
|
174
|
+
| pi session | LLM 运行事实(usage / stopReason / 耗时) | 有家(文档化) | **冷路径读**(单一适配器) |
|
|
175
|
+
|
|
176
|
+
### 5.3 数字三类(接 `design.md`《数字的归宿》)
|
|
177
|
+
|
|
178
|
+
- **A 预估/推算值** → 只作可读性便利,**不得作验收口径**(§77 原文不变);
|
|
179
|
+
- **B 捕获一手实测** → 落既有事件行(唯一落点);
|
|
180
|
+
- **C 可重算派生** → **不落盘**,`--report` 现场算。
|
|
181
|
+
|
|
182
|
+
### 5.4 人类专用面显式登记
|
|
183
|
+
|
|
184
|
+
stall/超时等 loop 侧事件标签只在日志里可读,**不假装可派生**;将来若确有"无家且需要"的量,按"家有无"闸门加**一个**字段——**留门,不预建**。
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 6. 分歧与收敛记录(供审阅)
|
|
189
|
+
|
|
190
|
+
- **A/B 之争(捕获式 vs 派生式)反复四轮**,最终形态是**按字段"家有无"分流**(不是整体二分)。反复的根因是**前提未核实**:§77 读法、session 格式是否文档化、`tok`/`err` 可否恢复——前提逐条核实后收敛。
|
|
191
|
+
- **前提钉死(无新证据不再重开)**:① §77 管估算值、不管一手登记与消费;② `tok`/`err` 在 session 有家(官方文档化 schema,实测 27ms/3MB);③ `elapsed`/`rc` 无家(loop 唯一见证);④ 冷路径读文档化平台格式是允许的(依赖类别已存在:fork 构建本就解析 session)。
|
|
192
|
+
- **记录纠正(三方各自的自我更正)**:铁律修正 §77 过宽读法;简单撤回"知识依赖/脆弱处"论证(未经核实假设);性能 0011 的三字段接受系消息交叉、依据消失后不坚持;铁律纠正简单 0011 §四.4"报告零 session 依赖"的内部矛盾(三字段条件误抄),简单认领。
|
|
193
|
+
- **过程教训**:整体二分命名(捕获式/派生式)两轮同名不同义——收束稿不采用整体形态名,直接写"按 owner 域取数"。
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 7. 未覆盖与诚实边界
|
|
198
|
+
|
|
199
|
+
- 本轮**只提建议、不改代码**:全部条目**未实施、未测试**;`--report` 的实现工作量与输出格式未评估。
|
|
200
|
+
- **上游格式长期稳定性无证据**:以"文档化字段 + fail-open → n/a"兜底;文档化 ≠ 冻结(本仓已为旧式 `sessionID` 留过兼容分支)。
|
|
201
|
+
- 未验证:pi 事件流/session 在版本间的字段变化;per-response 明细的具体实现(留门)。
|
|
202
|
+
- "有效性"的**另一半(结论质量)**不属日志系统议题,未展开。
|
|
203
|
+
- 第二批(三样观测面 + viewer 展示)的具体设计未展开。
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 附录:关键实测数据(本轮核实)
|
|
208
|
+
|
|
209
|
+
| 数据 | 来源/口径 |
|
|
210
|
+
|---|---|
|
|
211
|
+
| 44 分钟讨论:框架空闲 **3 秒(0.1%)**;196 次 LLM 响应(p50 8.4s);**5 次 provider 失败 ≈11% 墙钟**零记录;3 loop 并行度 2.09 | e2e13 时间流采样(2026-09-11) |
|
|
212
|
+
| session 解析:**3.00MB / 2031 条目,全量 `json.loads` 27ms(109 MB/s)** → 报告一次性 ≈0.1–0.3s | 性能视角实测(本分析 mid-run) |
|
|
213
|
+
| wake-logs:3.4–4.0KB/唤醒;PROMPT 段 ≈40–43% 重复 | 简单/性能实测 |
|
|
214
|
+
| pi session 格式**官方文档化**:`docs/session-format.md` 16.7KB(L87 `usage` / L88 `stopReason` / L209 样例) | 简单/铁律核实 |
|
|
215
|
+
| 本仓已 3 处解析 session:`meeting_fs.py:534`(会话头)/ `:810`(fork 构建,全部条目)/ `:980`(handoff) | 铁律核实 |
|
|
216
|
+
| 观测面契约缺失:`loop-*` / `wake-logs` 在 `design.md` / `README.md` grep **零命中** | 简单核实 |
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 多视角自审存档(docs/reviews/)
|
|
2
|
+
|
|
3
|
+
本目录保存**本项目用多视角机制审阅自身实现**产出的报告——即"吃自己的狗粮"
|
|
4
|
+
(dogfooding)的实证记录。每份报告都是一次真实 pi 讨论的 `result.md` 原文,
|
|
5
|
+
未做删改(只加本说明与文件头)。
|
|
6
|
+
|
|
7
|
+
## 为什么存档
|
|
8
|
+
|
|
9
|
+
1. **机制有效性的证据**:这些报告不是模拟,是三个视角 agent(性能 / 可读性 /
|
|
10
|
+
铁律)真实读代码、交叉引用、交锋收敛的产物——报告里的问题清单确实抓到了
|
|
11
|
+
真实缺陷(见下)。
|
|
12
|
+
2. **决策溯源**:修复批次(commit)常引用报告中的编号(如"P0/P1/P2"),
|
|
13
|
+
保留原文才能核对当时的事实与推理。
|
|
14
|
+
3. **方法论的样本**:报告本身演示了"观察句 vs 机制句"的证据强度要求
|
|
15
|
+
(`docs/test-methodology.md` 方法 16)——其中也有几处机制句在讨论中被
|
|
16
|
+
交叉复核推翻,是活教材。
|
|
17
|
+
|
|
18
|
+
## 索引
|
|
19
|
+
|
|
20
|
+
| 文件 | 主题 | 抓到的问题 | 修复 commit |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| `2026-09-10-e2e10-fork-source-modes-review.md` | fork 源模式描述与实现一致性(`docs/design.md` / README / AGENTS.md vs 代码) | 非法 `forkMode` 无 fail-fast(静默走"边界后全量"混合分支 → 930k tokens 超窗);wrapper 静默丢弃 `--fork-mode`;文档数字混源无锚;三处"完整上下文"与有损默认矛盾 | `732fdff`(P0/P1/P2)、`fe1955d`(方法论 11–14) |
|
|
23
|
+
| `2026-09-10-e2e11-forkmode-guards-review.md` | forkMode 四层守卫与默认值单一源(含文档口径与方法论条目) | compaction 模式产物含**重复** compaction 条目;budget 窗口含 compaction 时 pi replay **静默丢弃前缀(含 preface)**;台账 `dropped` **双重计数**;`FORK_MODES` 与分派**非结构耦合** | `9412e32`(P1–P3 + I1–I5 不变量 + 台账) |
|
|
24
|
+
| `2026-09-11-e2e14-observability-review.md` | 本项目可观测性自审(日志系统是否合理 + 如何监控讨论有效性) | 失败/重试不可见(provider error 零记录,实测占墙钟 11%);耗时不可度量;token 无出口;**`--wait` 用 `glob(loop-*.log)` 兼任判定输入**;viewer 与 check_status 的 done 判据分叉;两份逐字相同的 `log()`;观测面契约在文档零命中 | 见「e2e14 修复」提交(第一批 §3.1–§3.5) |
|
|
25
|
+
| `2026-09-11-e2e13-code-review.md` | 全项目代码合理性评审(第二轮,含时间流实测) | `--agents` 非法名在 spec-gen 路径炸出 traceback + 半成品;viewers/agents 校验与列举多实现(隐藏文件静默入场);wrapper 越界检查第三方扩展配置;`--start` 静默删 spec;session 文件两套查找规则;切换叙事拼 agent 定义正文 | `fe3aa09`(R1–R8) |
|
|
26
|
+
| `2026-09-10-e2e12-code-review.md` | 全项目代码合理性评审(性能 / 简单 / 铁律) | `_lock_git` 守卫 **fail-open**(锁态下 git 上溯到主项目仓库);`pgrep -f` 自匹配;`protocol.json` 读取散落 6 处 / 3 种失败语义(含 `result_writer` 默认值求值缺陷);`check_status` 用 `git grep` 全文误报 done;engine 直做 fs I/O;两处 git 入口加固不一致 | 见「e2e12 修复」提交(批 A/B/C) |
|
|
27
|
+
|
|
28
|
+
## 环境口径(读报告时的背景)
|
|
29
|
+
|
|
30
|
+
- 报告中的路径(如 `/tmp/mv-e2e9/disc4`、`discuss-mv-main-*`)是当时的**现场**,
|
|
31
|
+
按要求已清理;数字(条数 / MB / tokens)随主会话增长漂移,引用时请对照
|
|
32
|
+
`docs/design.md` §二的规模口径(产物侧数字随时间变化,消费侧数字必须带
|
|
33
|
+
唤醒序号)。
|
|
34
|
+
- 报告里出现的讨论消息编号(`可读性/0005`、`铁律/0007` 等)指向当时的
|
|
35
|
+
讨论消息文件,讨论目录已删——这些编号**不可再核验**,仅作溯源线索
|
|
36
|
+
(与代码注释引用约定一致:行为以自描述为准)。
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# 测试方法论细节(AGENTS.md 引用的配套文档)
|
|
2
|
+
|
|
3
|
+
> 本文件是**方法条目仓库**:具体怎么做、历史教训的实例细节都在这。
|
|
4
|
+
> AGENTS.md 只放核心制度与引用,不直接堆方法(用户 2026-09-09:100
|
|
5
|
+
> 个方法都写进 AGENTS.md 会无限膨胀)。方法条目在此新增,AGENTS.md
|
|
6
|
+
> 不逐条同步。
|
|
7
|
+
|
|
8
|
+
## 总原则(错误→制度化)
|
|
9
|
+
|
|
10
|
+
出现错误后要记的不是"这个 bug 怎么修",而是**什么制度缺失让它漏过、
|
|
11
|
+
让定位变慢**——补制度,保证同类错误结构性不再犯。复盘四问:
|
|
12
|
+
|
|
13
|
+
1. **哪个特性/改动引入了这个错误面?它的隐含假设是什么**(如"agent
|
|
14
|
+
名是 ASCII")——引入时有没有补这个假设的边界测试?
|
|
15
|
+
→ 制度:特性落地必须显式盘点它打破的既有假设并补对应边界测试。
|
|
16
|
+
2. **定位用了正确的方法吗**?纯机制层 bug(git/文件/编码/状态机)必须
|
|
17
|
+
用确定性复现(单测/小实验/装置),禁止用真实 LLM e2e 碰运气(不可控、
|
|
18
|
+
慢、贵,只做最终确认)。
|
|
19
|
+
→ 制度:bug 先按"机制层 vs LLM 行为层"分类选复现手段;机制层 3 分钟
|
|
20
|
+
内无复现即写最小实验,不靠长跑试错。
|
|
21
|
+
3. **失败现场保留了吗**?测试失败删 log = 测试白跑,只剩断言消息无法
|
|
22
|
+
定位。
|
|
23
|
+
→ 制度:测试装置失败必须保留现场(目录+日志),成功才清理。
|
|
24
|
+
4. **改装置/用新 API 先验证了吗**?先查文档或写秒级最小实验确认 API
|
|
25
|
+
语义,不写完整代码跑长测试试错(_outcome 时序两次实测失败后改用
|
|
26
|
+
sys.exc_info——先做 0.1s 实验即可知)。
|
|
27
|
+
|
|
28
|
+
> 编号 1–10 来自 pi-agents-helper 演化期与本项目早期;11–17 为 2026-09-10
|
|
29
|
+
> 两轮自审(e2e10/e2e11,多视角评审本项目自身实现)产出——那两轮暴露的
|
|
30
|
+
> 一类问题是"**制品文本**的断言失真与口径缺失"(规格/文档/日志层),
|
|
31
|
+
> 与 1–10 的"运行/装置层"互补。
|
|
32
|
+
|
|
33
|
+
## 方法条目
|
|
34
|
+
|
|
35
|
+
### 1. git 写前 pull 是硬规则
|
|
36
|
+
|
|
37
|
+
测试写消息统一走 `tests/test_meeting_concurrency.py` 的 `write_msg`
|
|
38
|
+
helper(写前 pull + commit + push,与生产 `commit_new_files` 同构)——
|
|
39
|
+
多 work 交替写不 pull 必然 push rejected。新测试写消息一律用它。
|
|
40
|
+
|
|
41
|
+
### 2. 进程检测
|
|
42
|
+
|
|
43
|
+
`pgrep -f` 会匹配运行它的 bash 包装自身 → 用方括号技巧
|
|
44
|
+
(`ps aux | grep "[x]xx"`)或精确 PID。**杀 loop 必须连带杀其 pi 子进程**
|
|
45
|
+
(先 `ps --ppid` 收集子进程再一起杀;pi 命令行不含讨论路径,按路径 grep
|
|
46
|
+
会漏检);清理后残留检查必须覆盖 pi 进程形态。
|
|
47
|
+
|
|
48
|
+
### 3. 失败三分类
|
|
49
|
+
|
|
50
|
+
设计漏洞(修设计+实现)/ 实现误判(撤回回正确实现)/
|
|
51
|
+
测试问题(修测试/装置)。在错误归因上堆补丁会越改越乱。
|
|
52
|
+
|
|
53
|
+
### 4. 装置对齐生产形状
|
|
54
|
+
|
|
55
|
+
setup_env 用生产 `gen_protocol`;消息产物用 `write_msg`——装置与生产
|
|
56
|
+
路径不一致会掩盖真实 bug 或制造假失败。
|
|
57
|
+
|
|
58
|
+
### 5. 测试运行/观察分离(tests/run_tests.sh)
|
|
59
|
+
|
|
60
|
+
层 1(默认)每次真跑 + tee tests/.cache/last.log,观察从文件读(0 秒);
|
|
61
|
+
层 2(显式 --reuse)指纹未变 + 上次 OK 才跳过(有掩盖风险,默认关);
|
|
62
|
+
最终回归/诊断必须 `--force` 真跑。
|
|
63
|
+
|
|
64
|
+
### 6. 参数形态矩阵覆盖(二维)
|
|
65
|
+
|
|
66
|
+
路径/名字类参数测试覆盖全部调用形态:
|
|
67
|
+
- 维度 1 写法:绝对 / ./相对 / 裸名
|
|
68
|
+
- 维度 2 **字符集:ASCII / 中文**——git quotepath 只转义非 ASCII
|
|
69
|
+
(中文名拿到带引号路径 → list_my_messages 恒空 → is_first 恒真 →
|
|
70
|
+
freezing 卡死,2026-09-09 e2e 实测;纯机制层 bug 本可用单测抓住,
|
|
71
|
+
viewers 中文名是产品核心,不是 exotic case;单测先于 e2e 的原则见
|
|
72
|
+
总原则 2)
|
|
73
|
+
- 删代码时检查相邻代码块是否被连带误删(上游实测教训:abs_dir 规范化
|
|
74
|
+
被连带删除后静默潜伏 3 天)。
|
|
75
|
+
|
|
76
|
+
### 7. 测试不留 session(用户 2026-09-04 定)
|
|
77
|
+
|
|
78
|
+
pi 冒烟/连通性测试产生的 session 是垃圾——测试结束后清理自己产生的
|
|
79
|
+
session(含主 pi 侧 `~/.pi/agent/sessions/<编码目录>/`,不只讨论目录
|
|
80
|
+
pi-sessions)。注意与"失败保留现场"(总原则 3)的边界:失败现场在
|
|
81
|
+
bug 定位完成前保留;成功/定位完成的 session 随手清。
|
|
82
|
+
|
|
83
|
+
**清理验收机制(2026-09-09 建)**:靠记忆逐项检查必然漏(afk-e2e 引导
|
|
84
|
+
session 漏删即反例——两代项目重复犯)。测试/验证结束跑
|
|
85
|
+
`scripts/check-residue.sh`(残留检查器):一条命令报告三类残留——
|
|
86
|
+
主 pi 侧近 24h 非白名单 session / loop+pi 进程 / mv-* 目录,
|
|
87
|
+
输出"干净"或残留清单。退出码 0=干净 1=有残留。
|
|
88
|
+
|
|
89
|
+
### 8. 测试与产品目录隔离 + 用例级 teardown
|
|
90
|
+
|
|
91
|
+
wrapper/spec/环境类测试在 scratch 项目目录跑(临时 git 仓库 + 临时
|
|
92
|
+
session),不污染真仓库;每个用例结束**立即删除自己的产物**
|
|
93
|
+
(setup→act→assert→teardown 闭环),再跑下一个——产物路径同名也无所谓
|
|
94
|
+
(已被释放),不靠 sleep 赌时间戳(反例实测 2026-09-09:两条 prepare
|
|
95
|
+
同秒写同一 mv-spec-* 目录互相叠写,因验证命令未做用例级清理)。
|
|
96
|
+
|
|
97
|
+
### 9. 失败保留现场(2026-09-09 定)
|
|
98
|
+
|
|
99
|
+
concurrency 等测试装置 finally 用 `sys.exc_info()[0] is not None` 检测
|
|
100
|
+
失败——保留目录 + fake-*.log 供调试,成功才 rmtree;失败路径不得
|
|
101
|
+
return(会吞正在传播的异常 → 假通过)。实测不可靠方案:`_outcome`
|
|
102
|
+
`.result.errors`(finally 时未填充,时序在测试方法之后)、addCleanup
|
|
103
|
+
检测(共享 result 下跨测试累积误判)——不要再用。
|
|
104
|
+
|
|
105
|
+
### 10. 外部契约按字段拼接,不按值形状猜(2026-09-10)
|
|
106
|
+
|
|
107
|
+
消费外部接口(env 变量/配置文件/session 事件)时,按其**定义字段**
|
|
108
|
+
取值拼接,不得按值的形状(含不含某字符)猜语义。反例:pi 契约是
|
|
109
|
+
`PI_PROVIDER`=provider + `PI_MODEL`=模型 id(id 本身可含 `/`,聚合类
|
|
110
|
+
provider 的命名空间 id 如 `deepseek/deepseek-v4-flash`);旧代码用
|
|
111
|
+
"含 `/` 就视为完整 ref"的形状启发式 → provider 被丢掉 → pi 按
|
|
112
|
+
`provider/id` 解析到**同名的另一个 provider**——不报错、行为相似,
|
|
113
|
+
静默失真(2026-09-10 用户质询 model 是否跟随主 pi 时实测暴露;
|
|
114
|
+
不在场的单测从未覆盖含斜杠 id 形态)。制度:①拼接函数统一且幂等
|
|
115
|
+
(已带前缀不重复拼);②契约字段的形态矩阵(纯 id / 含斜杠 id /
|
|
116
|
+
已带前缀 / 字段缺失)必须进单测——形状假设即未经测试的假设。
|
|
117
|
+
|
|
118
|
+
**本例的真实来源(移植偏差,2026-09-10 git 溯源)**:上游 bash 实现是
|
|
119
|
+
正确的总是拼接(`full_model="$provider/$model"`);2026-09-09 移植进
|
|
120
|
+
python 时被"顺手改写"为含斜杠则不拼——**移植不是重写**:逐句对照、
|
|
121
|
+
每处变动都要能说出理由并补测,否则静默引入语义变更(本例潜伏 1 天,
|
|
122
|
+
因错 ref 仍解析到可用模型而无人发现)。
|
|
123
|
+
|
|
124
|
+
### 11. 断言强度与范围 ≤ 保证强度与范围(2026-09-10 e2e10 评审)
|
|
125
|
+
|
|
126
|
+
写下的每一句断言(代码注释/docstring/日志/文档/横幅),其**强度**与
|
|
127
|
+
**范围**不得超过代码能保证的强度与范围。三个维度、各需在**写时**校核:
|
|
128
|
+
|
|
129
|
+
| 维度 | 校核时机 | 反例(本轮) | 正解 |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| 强度 | 写时 | "携带**完整**上下文"(实际默认 budget 有损:丢弃 2146 条、省略 1071 处);"统计取自**同一来源**"(实际还有构建返回值);"[start] **已启动** N 个 loop"(未校验存活) | 强度降级到可实现的事实("按预算裁剪+折叠";"已拉起 N 个进程(存活未校验)") |
|
|
132
|
+
| 范围 | 写时 | "**不依赖任何扩展**"(构建期成立,运行期上下文仍受环境扩展渲染期裁剪影响) | 限定作用域("构建期不依赖…;运行期受…影响") |
|
|
133
|
+
| 引用完整性 | 被引用物状态变化时 | 注释写"单一心智模型(curated 不保证…,**docstring 已声明**)"而 docstring 中并无该声明;改名后旧名引用残留 8 处 | 被引用物变化 → **同批**更新全部引用处(撤回传播:引用已撤回断言的文本同样要改) |
|
|
134
|
+
|
|
135
|
+
判据须可指认"位置 + 保证",不用于文风评审;本体范围 = 制品文本
|
|
136
|
+
(讨论消息天然含试探与修正,不逐句约束)。
|
|
137
|
+
|
|
138
|
+
### 12. 改名/值域变更的完成判据 = 分区语义自检(2026-09-10 e2e10 评审)
|
|
139
|
+
|
|
140
|
+
改一个对外取值(模式名/枚举/default)时,完成判据不是"我觉得都改了",
|
|
141
|
+
而是**一次 grep 能判**的分区自检:
|
|
142
|
+
|
|
143
|
+
- **现行描述区**(源码注释/docstring/CLI help/测试命名/README/AGENTS/
|
|
144
|
+
design.md 正文):旧名**必须为空**
|
|
145
|
+
- **允许保留区**(`docs/examples/` 存档、design.md 决策记录、git 历史、
|
|
146
|
+
测试里的非法值反例、**面向用户的迁移提示文本**——错误信息里有意提及
|
|
147
|
+
旧值以指引迁移,如"若是旧版产物请清理后重跑"):**必须保留**(否则规则
|
|
148
|
+
活不过第一次运行)
|
|
149
|
+
|
|
150
|
+
配套硬教训:**"值只存在于每次生成的产物里,无迁移负担"是错的**——值一旦
|
|
151
|
+
写盘就会存活(实测 `/tmp` 下 rename 前的 `protocol.json` 里
|
|
152
|
+
`"forkMode": "curated"` 仍在,重跑即命中静默走偏分支)。因此值域变更必须
|
|
153
|
+
同时加**解析入口守卫**(非法值就地报错,见方法 13)。
|
|
154
|
+
|
|
155
|
+
### 13. 配置值入口守卫:源头报错,不进重试路径(2026-09-10 e2e10 评审)
|
|
156
|
+
|
|
157
|
+
对外配置(`protocol.json` 的 `forkMode` 等)的合法值判定只在**实现处**
|
|
158
|
+
定义一次,并在**每个解析入口**设守卫;非法值就地失败,**不得**落到
|
|
159
|
+
"未匹配任何分支"的隐式路径。反例:`build_fork_source` 只对三个字面量
|
|
160
|
+
分支判断、无 else → 非法值静默走"边界后全量、不折叠、无预算"混合分支
|
|
161
|
+
(实测 6.13MB/930k tokens 超窗),且被 engine 的统一异常边界吞成
|
|
162
|
+
"每 2s 廉价重试",600s 后以 `reason=stall` 收尾——**错误归因误导未来
|
|
163
|
+
读者**。守卫布点(四层,一次做完):
|
|
164
|
+
|
|
165
|
+
| 层 | 动作 | 理由 |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| wrapper(bash) | **哑转发**余参,不解析值、不注入默认值 | 转发者只转发;默认值单一源在 python |
|
|
168
|
+
| CLI(argparse) | `choices` 引用常量 | 人输入早期反馈 |
|
|
169
|
+
| 常驻入口(`__main__`) | 非法 → `[fatal]` + 非零退出 | 配置错误不是运行期故障,不进 engine 重试路径 |
|
|
170
|
+
| 实现函数(fs 层) | 入口(I/O 之前)值域校验 | 值集合定义权在实现处;库调用者同样受守卫 |
|
|
171
|
+
|
|
172
|
+
测试要求:每层各一条(fs 层断言"报错且**目标文件未创建**";入口层用
|
|
173
|
+
真实 subprocess 断言非零退出 + `[fatal]` + 无副作用;wrapper 层断言参数
|
|
174
|
+
**确实到达**下一层)。
|
|
175
|
+
|
|
176
|
+
### 14. 数字必须带(口径;测点),且判据要挂在提交动作上(2026-09-10 e2e10 评审)
|
|
177
|
+
|
|
178
|
+
写进文档/报告的数字,格式为**数字(口径;测点)**——"同处"是关键:
|
|
179
|
+
脚注、别节、口头都不算。反例(同一提交内):`docs/design.md` §二 明文
|
|
180
|
+
要求"引用必须带唤醒序号""实测须附口径与测点",而 §一 的规模表混用了
|
|
181
|
+
不同产物的条数/MB 与不同时刻的 tokens,且无锚。根因不是不知道规则,而是
|
|
182
|
+
**判据没有挂在提交动作上**(§二 是声明,不是检查点)。
|
|
183
|
+
|
|
184
|
+
三条配套约束:
|
|
185
|
+
|
|
186
|
+
1. **产物侧数字**(条数/MB/est)随源漂移 → 整节加统一锚
|
|
187
|
+
("产物侧,<日期>,主 session ≈N 条");**预算钉住值**(如 est)显式
|
|
188
|
+
标注"不随源漂移"。
|
|
189
|
+
2. **消费侧数字**(tokens)必须带唤醒序号(w1/w5…),否则不可比。
|
|
190
|
+
3. **示意值须三约束**:标"示意"、指向权威口径、声明"不作验收口径"。
|
|
191
|
+
|
|
192
|
+
数字四分类(写之前先问"它属于哪类"):配置派生(附推导式与重估触发)/
|
|
193
|
+
实测(附口径与测点)/外推(附依据与触发)/事后拟合关系(标注"事后归纳,
|
|
194
|
+
非原始设计目标"——不得用它追溯解释当初取值)。
|
|
195
|
+
|
|
196
|
+
**改数字前先判性质**:是**漂移**(同口径新测)还是**复测**(方法/装置
|
|
197
|
+
变化)?无锚旧值按**修订记录**处理——并列新值与旧值,并注明旧值口径
|
|
198
|
+
不可考(不得静默替换,否则读者无法判断口径是否已变)。
|
|
199
|
+
|
|
200
|
+
### 15. 触发条件必须指名一个现在就能用的观测点(2026-09-10 e2e11 评审)
|
|
201
|
+
|
|
202
|
+
任何带触发条件的记录("当 X 超过阈值时再优化")必须能回答:**现在用什么
|
|
203
|
+
命令/日志能观测到 X?** 观测手段不存在 = 触发条件不是判据而是修辞。反例:
|
|
204
|
+
记录写"观察 `status-*.json` / 日志即可估 RSS 与耗时",而 `status-*.json`
|
|
205
|
+
只存 `sessionID`、`log()` 只打时间戳——该判据永久不可执行。修法二选一:
|
|
206
|
+
(a) 补一个**最小观测点**(如首唤日志加"构建 181ms/55MB"一行;不建指标
|
|
207
|
+
体系、不进 status);(b) 把触发条件改写为可执行的复测流程("插桩后复测
|
|
208
|
+
占比"),并标明它是**复测型**而非监控型。
|
|
209
|
+
|
|
210
|
+
### 16. 评审层表述纪律:观察句与机制句分级举证(2026-09-10 e2e11 评审)
|
|
211
|
+
|
|
212
|
+
讨论消息中的句子会流向制品(docstring / 决策记录 / 修复动机),因此**在
|
|
213
|
+
讨论中就要按制品的举证强度写**:
|
|
214
|
+
|
|
215
|
+
- **观察句**("实测产物 body 3129 条,`c5d43076` 出现在 [0, 805]")——
|
|
216
|
+
必须可复现(注明命令/口径);
|
|
217
|
+
- **机制句**("副本是不可达的孤立节点")+ **承载严重性/修复动机**的句子
|
|
218
|
+
——必须**推演过或引源码**,否则显式标注"推断";机制句带**证据指针**
|
|
219
|
+
(源码位置/探针命令)优于只写"推断"标记(标记会忘、指针仍在)。
|
|
220
|
+
|
|
221
|
+
本轮出现两例机制句失真("孤立节点"、"parentId 指向区外"),均在被引用
|
|
222
|
+
进 docstring 前由交叉复核拦下——机制句的传播链上受同等强度约束。
|
|
223
|
+
同时:**引用已撤回的测量/方案时,不得并入成稿**(性能侧对已撤回"锚点
|
|
224
|
+
改写"的 0.07/0.27ms 测量即属此类)。
|
|
225
|
+
|
|
226
|
+
### 17. 修复顺序纪律:结构修复与其事实基础/声明文本同批(第三次应用)
|
|
227
|
+
|
|
228
|
+
多个修复若共享同一事实基础(同一计数器、同一不变量、同一动机句),必须
|
|
229
|
+
**同批落地**——否则验证时"先红后绿"的红色可能来自旧 bug,绿色可能来自
|
|
230
|
+
侥幸,观察对象失真。本轮实例:① `dropped` 计数重算必须与"comp 移除计数"
|
|
231
|
+
同批(否则 I5 台账断言被 stale binding 污染);② 修复动机句必须基于
|
|
232
|
+
**修正后**的事实("满足 I1、消除 last-wins 依赖"而非已被推翻的
|
|
233
|
+
"修复 replay 破坏");③ 声明文本(docstring/§一 表)与产物行为同批。
|
|
234
|
+
|
|
235
|
+
**反向断言(先红后绿)是 fixture 的存在条件**:新写的回归测试必须在
|
|
236
|
+
修复前的代码上**确实失败**(可用 `git show HEAD:<file>` 临时回退验证)。
|
|
237
|
+
只绿不红的测试无法证明它覆盖了该缺陷。
|
|
238
|
+
|
|
239
|
+
### 18. 批量文本替换必须验证生效(`str.replace` 静默失配)
|
|
240
|
+
|
|
241
|
+
**背景**:脚本化编辑用 `s.replace(old, new)` 时,若 `old` 不匹配(空白/换行/
|
|
242
|
+
前批次已改过该处),Python **不报错、静默无事发生**——"改动做了"是错觉,
|
|
243
|
+
而后续代码可能已经引用了"以为加上了"的名字。
|
|
244
|
+
|
|
245
|
+
**实测代价**(2026-09-11 同一会话两次,均由**真实调用路径**抓出):
|
|
246
|
+
- ① 删除函数用索引切片 `s[i:j]`,`j` = "下一个锚点函数"位置——但两个小 helper
|
|
247
|
+
恰在区间内 → `_dur`/`_hhmm` 被连带删除(NameError 暴露);
|
|
248
|
+
- ② 加 import 的 `replace` 因前一批次已在同处加过一行而失配 → engine 调用了
|
|
249
|
+
**未导入**的 `core_meeting_speak_count` → **真实 loop 在配额判断处崩溃**
|
|
250
|
+
(fake agent 多进程测试抓到收敛失败;纯静态检查全绿)。
|
|
251
|
+
|
|
252
|
+
**方法**:
|
|
253
|
+
1. **每个替换后断言**:`assert old in s, old[:80]`——失配立即炸,不静默;
|
|
254
|
+
2. 多处编辑**优先字符串对替换,不用索引切片**(`s[i:j]` 的边界由别处代码决定,
|
|
255
|
+
改动中间地带就会误伤);
|
|
256
|
+
3. 删除函数区间用"下一个函数名"作终点前,先 `grep` 确认区间内没有别的定义;
|
|
257
|
+
4. 提交前跑**真实调用路径**:`python3 -c "import <mod>"`(抓未定义/未导入名)
|
|
258
|
+
+ 相关测试。两次失误都是真实路径抓到的——静态阅读与"看起来对"都不够。
|