# Changelog 本插件遵循语义化版本。完整设计文档见 [`docs/`](./docs/),工程方法论见 [METHODOLOGY.md](./METHODOLOGY.md)。 ## [0.29.0] — 2026-09-18 > **一句话**:不传 `background` 现在也走后台——长任务不再被插件自己的 540 秒内部截止掐掉、也不再撞平台 600 秒墙钟连报告一起丢;短任务可用 `background: false` 强制同步。**本版改 `lib/**` ⇒ 重启 DSH 后生效。** **批 21(长任务默认走后台 · dsh 子代理路径的三态语义 · **带一处重要环境发现**)** - **【计数行】**:`node --test` **509/509**(**基线 497 + 本批 12**)。⇒ 台账 §三:`advisor-config.test.mjs` **60 → 67** · `codex-runner.test.mjs` **185 → 190**(新增 **12** 条验收腿;另有 **5 行既有断言翻预期**——FR-2 的必然后果,翻预期不增计数)。 - **立项形态**:`eng_coder` 与 `escalate` 各有两条后端。**codex 后端早已按预算默认后台**(缺省预算 1800s / 600s,都高于 `budgetCapMs` 的 540s 阈值),**advisor 按预算自动**、**consult 无条件后台**;**只有 dsh 子代理路径默认同步**(父代理阻塞,内部截止 540s < 平台 600s 墙钟)⇒ 长任务只有两种死法:**被掐掉**或**撞墙钟连报告一起丢**。 - **四处手术(零新配置键,靠三态语义)**:**①** `lib/eng.mjs` 的 dsh 判定 `=== true` → **`!== false`** · **②** `lib/escalate.mjs` 同族同改 · **③** `lib/index.mjs` 两处 schema **删掉 `default: false`**(宿主该注解是**非校验**的:`dsh-tools` 的 `schema.d.ts:14` 字面「Non-validating default annotation」⇒ 它不改变行为,但留着会让**给模型看的 schema 撒谎**) · **④ ★ `lib/index.mjs` 的 escalate 透传点 `=== true` → 三态透传**——**不改这一行,escalate 的翻转会完全静默失效**(`undefined` 被压成 `false`)。**③④ 是父侧漏看、会诊抓到的。** - **三态语义**:**省略 ⇒ 后台**(新默认)· **`false` ⇒ 强制同步**(逃生口)· **`true` ⇒ 后台**;**非布尔的 `background` 一律按「非 false」走后台**(fail-safe,不静默同步)。 - **配套三件(缺一不可)**:**① 跨机制护栏**(`lib/advisor.mjs`)——默认后台后父代理不再阻塞,**它可能在交付落地前跑 code 评审**,而重置轮次/prior、合并 `touchedFiles` 的簿记发生在 job settle 的回调里 ⇒ 早跑的评审**看到的文件不对 + prior 被污染 + 那轮白跑**;护栏 = code 型评审发起前查 **eng/escalate 槽位**,在飞则**拒绝并指向 job id**(design 型不受影响)。**② 回落告警随工具返回可见**(此前只在 `console.warn` ⇒ 翻转后会变成「父代理以为派了后台、傻等一个不会来的通知」的**静默失败**);**行为仍是回落同步、文案逐字节不变**。**③ 描述面六处** + 提示词**过渡态与隔离期纪律**。 - **不动**:codex 路径判定(零漂移)· `budgetCapMs` 缺省 540s(**60 秒余量是「回落了也拿得到诊断」的保证**)· `session-state` / `stages` / `stage-gate` / `path-kind` / `preset-static` 等**未授权基线档零改动**。 - **★★ 同批修掉的一个真根因(本版最值得记的一条)**:本机**此前在运行进程里取不到 `ctx.jobs`**——证据是**显式传 `background: true` 也走同步路径**(两次真实派发都以同步路径的失败信封结束)。⇒ **「长任务超时」的原痛点主因是该服务在运行时不生效,不是默认值**;**本批的默认翻转只有在该服务可用之后才真正生效**。处置:显式挂载 `@deepseek-ai/dsh-jobs-local` + 重启,并以一次**真实探测**验证「**不传 `background` 也能真的派出后台 job**」。⇒ **本批的价值之一是它把这条静默失败面照亮了**(若没有 FR-3 那条「回落告警进工具返回」,这个根因还会继续藏在 console 里)。 - **★ 过程如实登记**:实施跨 **4 次派发**,其中**两次以 `error` 零产出结束**(工作树干净、无残留)——**根因即上面那个服务未挂载**。**独立分歧审计 5 条**(**1 🔴 = 父侧收口欠项**:交接页没有本批重启行;**2 🟡**:设计档自己写的 **N9 负控字面形态与原实现语义全等 ⇒ 恒绿**(改为意图形态后才恰红 AC-15)· **5 行既有断言被翻转而三处记录都没写**;2 🔵)。**交付代码评审 `VERDICT: PASS`**(🟡1 · 🔵3,**全是注释/描述层滞后、零行为缺陷**)。**★ 审计的正面实证**:12 条新腿在**旧实现下恰 10 条转红、全部 `ERR_ASSERTION`、假红 0**;**10 条负控 + 5 条阴性对照零假红**(其中把 ④ 的透传改回 `=== true` ⇒ AC-2 转红,**三态透传的必需性得证**);回落文案**逐字节集合相等**;台账 **21 档双射、逐档全等、合计 509 == 509**;`AP_TEST_AUTHORIZED` **数组行逐字未动**。 - **★ 不可真机验证(如实登记)**:**重启后是否已加载新 `lib/**`**(T3 人工面;已用一次真实探测间接验证后台派发可用)· **在飞槽位重启失忆的发生率**(R-66)· **别的交错组合**(R-68)· **提示词的实际效果**(软约束,硬约束靠护栏)。 ## [0.28.0] — 2026-09-17 > **一句话**:写文件更抗折腾、失败不再静默:目标文件被杀软或索引器**短暂占用**时自动重试(8 次、约 0.3 秒),不再直接失败;**真的**失败时留下可检索的告警。顺带修掉设置页「恢复默认」**失败却报成功**的问题。 **本版改 `lib/**` ⇒ 重启 DSH 后生效。** **批 20(原子写的可重试与失败可见 · 本批改 `lib/**` ⇒ **重启 DSH 后生效** · **带未决项,见末条**)** - **【计数行】**:`node --test` **497/497**(**基线 485 + 本批 12**)。⇒ 台账 §三:**新档 `write-atomic.test.mjs` 11 条**(档数 20 → 21)· `config-api.test.mjs` **25 → 26**(`T-LC2` 等值锁同批同步)。 - **立项形态**:`R-51`(批 14)与 **`R-13`**(批 9 台账「已知 flake」)**实测同源**——**「时序 flake」是误诊**:失败原文 `actual ['keep','malformed','stale1','stale2']` **恰是「写落地前」**的文件内容 ⇒ 唯一路径是「**那次写没落地**」,而失败被 `session-store` 的 `catch → console.warn` 吞掉、**而该测试断言的是文件内容**。 - **★ 会诊三家一致:「只做重试就是超卖」⇒ 本批两条腿落地**:**① 有界重试**(`renameSyncWithRetry`:**8 × 平铺 30ms**、**按 `e.code` 字符串**白名单 `EPERM`/`EACCES`/`EBUSY`、**`ENOENT` 特判重铸 tmp**、耗尽**重抛原对象**(保 `.code`)+ 遥测 `code`/`errno`/次数/耗时 + 可 grep 签名);**② 失败可见**(遥测 + **`DELETE /config` 那句「面向用户的谎」被修**:清除失败 ⇒ `ok:false` + 500,对齐 PUT 先例)。 - **`writeFileAtomic` 重排**:`mkdirSync` **环外**(保 F12-T5 的 `ENOTDIR` 快失败)· **写与 rename 同入环内 `try`、失败即 `unlink`** · **tmp 名环外定格一次** · **孤儿清扫 ≥10min**(`.tmp-` 与 `.del-` 两形)。**`clearUserConfig`** 并入同一原语 + **TOCTOU 幂等**(`ENOENT` ⇒ 判「已清空」返回 true)。 - **契约零改动**:三个 store 的 `save*` **仍 `console.warn` + `return false`**(内存态是第一事实源、盘是镜像)。 - **注入缝族**:`{delays, maxAttempts, write, onEnoent, rename}`——`write` 钩子的**存在性**门控 `unlink(from)`,保护 `clearUserConfig` 的**活配置文件**;`rename` 缝使 rename 阶段可注入而**不必 `mock.module`**。 - **★ 姿态如实声明**:「**按机制假设根修**」,**不是**「已确诊 `EPERM`」——`R-13` 的**原发 errno 从未被捕获**;因此本批**自带观测面**(耗尽遥测),**复发时可判真伪**。 - **★ 本批的未决项(如实登记,不藏)**:**疗效门槛未达标**——协议(全量 ×10 + 单档 ×20)实测 **全量 9/10**(第 9 轮 `fail=1`,**失败项身份未知**:父侧脚本只记了计数、未捕获失败项名;**追加 20 轮 0 复现**,累计 30 轮 1 红)· 单档 **20/20 全绿**。⇒ **`R-13` 不宣告「已根治」**:台账 §五 写「**机制已根修(v0.28.0);疗效门槛未达标**」,且**该行保留**——删行会**打红发布门的三条机械断言**并牵动发布门本体与其基线档,**且会失去 `G6` 的复跑豁免,而那条未知偶发正需要它**。 - **★ 交付过程如实登记**:实施跨 **6 次派发**——第 1 次**零产出**(1800s 全烧在从四份文档重新推导设计)· 第 3 与第 5 次 **1800s 兜底、留半成品** · 第 2/4/6 次成功。**独立分歧审计 10 条**(**1 🔴 = 父侧在收窄派发时漏派 stage 3**,`DELETE /config` 的谎未修;另有两条 🟡 是**我自己设计的负控「写着但没人能执行」**)⇒ 修复轮补齐;**交付代码评审 `VERDICT: PASS`**(🔴0 · 🟡3 · 🔵4,7 条全处置)。**审计还实证**:删掉台账 §五 的 `R-13` 行 ⇒ 门测试 **pass 7 / fail 2**、`release-check` 报 **G6 FAIL** ⇒ 「不删行」的裁定**有机械后果为据**。 - **零改面**:`test/session-state.test.mjs`(**疗效见证者,一字未改**)· `test/release-check.test.mjs` · `release-check.mjs` · `test/fixtures/**` · `lib/advisor.mjs` · `lib/state.mjs` 逐档零改动;`lib/**` 六串零命中;`failStop(` 恒 **18**;零新依赖;**负控与变异全部在仓外副本上做,仓内零残留**。 ## [0.27.0] — 2026-09-17 > **一句话**:推理强度回落**不会再偷偷把推理关掉**:`off` 是「关闭开关」而不是「力度档」,所以回落**永远不落到 `off`**;显式要求关闭推理而模型做不到时,**会告诉你**并交还提供方默认。 **本版改 `lib/**` ⇒ 重启 DSH 后生效。** **批 19(effort 回落的开关语义:`nearestEffort` 根修 · 本批改 `lib/**` ⇒ **重启 DSH 后生效**)** - **【计数行】**:`node --test` **485/485**(**基线 477 + 本批 8**)。⇒ 台账 §三 `codex-runner.test.mjs` 用例数 **177 → 185** 同批同步(`T-LC2` 等值锁)。 - **立项形态**:`off` 是**关闭开关**而不是**力度档**,而 `nearestEffort` 让它在**同一把距离尺**上参与竞争 ⇒ 用户要「低推理」、拿到的是「**推理全关**」。触发条件实测为 `requested = low` ∧ `medium ∉ supported` ∧ `off ∈ supported`;穷尽 dsh 梯子的 31 个非空支持集,`low` 落到 `off` 的**恰 4 组**(退化集 + `{off,high}` / `{off,max}` / **`{off,high,max}`**)。本部署 `profile/settings.yaml` 的 20 个模型条目里 **8 个是该中招形状**,而 `profile/profiles/web/cordis.patch.yml` 的 convergence 组恰是 `glm-5.3-flash` 配 `low`、`engCoderEffort` 也是 `low` —— 只因 user 层覆盖成 `max`/`high` 才未中招。批 14(`v0.23.0`)只做了默认值层面的规避(`low` → `medium`)并把它另立批次(D14-2),本批即那一批。 - **★ 用户裁定的两条语义**(本批不自行拍定):**①** 回落**禁止**落到 `off`,**仅当支持集里根本没有力度档时才用**它(退化兜底,带响亮专属告警);**②** 显式请求 `off` 而模型**不支持** `off` ⇒ **改为省略 `reasoningEffort`**(`effort: null`,交还提供方默认),与 codex 侧既有的 `off ⇒ null` **值对称**。 - **★ 会诊抓到的第二个形状**(三家独立同结论,父侧实测复现):显式请求 `off` 而模型没有 `off` 时,现役代码会把它**改到力度档**(`{low,high}` ⇒ `low`、`{medium,high}` ⇒ `medium`、`{max}` ⇒ `max`)——**用户明确要求关推理,却被打开**;裁定 ② 把它一并修掉。**这不是规避性补丁,是语义修正。** - **实现(三级分层,全部住在 dsh 调用点)**:**`nearestEffort` 的签名与函数体逐字零改动、codex 调用点逐字零改动**(⇒ codex 侧的零连带是**结构性的**,不是「论证出来的零」)· 候选池按**名**分层(`!== DSH_EFFORT_OFF`;**禁按梯子下标**——codex 梯子 `[0]` 是 `low`,按下标会真打穿 codex)· **退化判定 = `degrees` 空且 `cands` 非空**(**不是**「`nearest` 返回 `null`」——后者会把 `{off, xhigh}` 判成非退化 ⇒ **透传一个已知不被支持的值 ⇒ 秒死路径**,且把差集从 3 组扩大)· **命中检查仍先于一切分层**(显式 `off` 受支持 ⇒ 原样返回、无 note)。 - **★ 核心可证性质:改动差集恒等于 3 组**(**读数口径 = `effort` 值面**):`{off,high}` ⇒ `high` · `{off,max}` ⇒ `max` · `{off,high,max}` ⇒ `high`;其余 28 个非空支持集的 `effort` 值**逐字不变**,退化集 `{off}` 的**值也不变**(只有措辞变)。父侧在**仓外副本**上穷尽复算,并独立复现了「旧实现下恰 13 条断言红」。 - **三条 note 措辞互斥分叉(这是退化支唯一的判别力来源)**:**普通回落**句**逐字保留**(既有断言依赖)· **退化兜底**支带专属子串「**退化兜底**」(该支的**值与改前相同** ⇒ 只钉值会恒真)· **显式 `off` 关不掉**支含「**未能关闭推理**」且**不含**普通回落句 ⇒ 两侧断言不会交叉误绿。 - **五处现役断言翻预期(逐条点名)**:`test/codex-runner.test.mjs` 的 `low` 对 `[off,high,max]` 三处(consult dsh 行 / eng dsh 分支 / advisor dsh 主路径)由 `"off"` 改 `"high"`;单元层 `b.effort` 由 `"off"` 改 `"high"`;单元层显式 `off` 对 `[low,high]` 由 `"low"` 改 `null`。**这是本批唯一的既有断言改动面**(父侧实测:只改这三处之外的两处会当场红)。 - **八条新验收用例**:A5 穷尽 31 集的**值面差集**(恰 3 组且逐组点名)· A6/A7 退化兜底与**措辞分叉**(含 `{off, xhigh}` 同落退化支)· A8 显式 `off` 关不掉 ⇒ `null` + 专属 note + `console.warn` · **A9/A10 接线级 `null` 出口 × 五个 dsh 消费点**(consult / escalate / eng / advisor 主路径 / advisor 智能回落轮),断言用 **`!("reasoningEffort" in agentOptions)`** 钉**键缺席**——「键在、值错」正是这类 bug 的病灶形态,钉值钉不住。 - **FR-4 描述面同批同步**:`lib/effort-resolve.mjs` 头注释的回落语义段 · `lib/eng.mjs` 的 `ENG_CODER_EFFORT_DEFAULT` 注释(原句在修复后**成为假陈述** ⇒ 改归 as-of 批 14 的历史记述)· `README.md` 的功能表行与 `engCoderEffort` 段。 - **零改面**:`release-check.mjs` 零改动 · `test/release-check.test.mjs` 零改动 · `test/fixtures/**` 零改动 · **零新增测试档**(20 档不变)· 六串零命中 · `failStop(` 恒 18 · 零新依赖 · `docs/consult-minutes/**` 只排除不修改 · **负控与变异全部在仓外临时副本 / 内存夹具上做,仓内零残留**。 - **★ 语义代价(如实登记,不许把宣言写得比机制大)**:`effort: null` **不代表「无推理」**,它代表「**交还提供方默认**」——对推理强制型模型,提供方默认档可能**比「最低受支持档」更贵**。「改落最低受支持档」的备选**已被否决**(理由:用户裁定 ②;`off ⇒ 省略` 两侧同形;不替用户拒绝的推理买单),**其代价如实登记为设计档 §9.4 残差 #5**。 - **★ 九条诚实残差**(设计档 §9.4,逐条可查)另含:`{off, xhigh}` 形状在本部署**零观测**(退化支取值只能由夹具证明)· codex 侧 `off ⇒ null` 的**沉默**本批**有意不解钉** · 元数据不可得时显式 `off` 仍原样透传(既有 fail-open 原则)· 设置页目录不显示 `off` · `eng.mjs` 的 `!== null` 判空与其余三处 truthiness 不同源 · 历史批次档保留旧陈述 · **未来的新 dsh 调用点若忘记分层会退回旧行为**(`nearestEffort` 仍是纯距离函数)。 - **★ 本批改 `lib/**` ⇒ 重启 DSH 后生效**(已写进交接页的重启清单;**不擅自重启**)。 ## [0.26.0] — 2026-09-17 > **一句话**:给 CHANGELOG 的计数行加了一道机器锁:绑定区域内的令牌**取值必须一致**(纯测试改动,无需重启)。 **批 18(唯一窄腿:**CHANGELOG 绑定区域内的令牌值一致** · 本批为**纯测试面** ⇒ **无需重启 DSH**)** - **【计数行】**:`node --test` **477/477**(**基线 476 + 本批 1**)。⇒ 台账 §三 `doc-hygiene.test.mjs` 用例数 **24 → 25** 同批同步(`T-LC2` 等值锁)。**★ 交付时点实测:476 pass / 1 fail —— 唯一红不在本批的腿上,而在批 17 注册的两个**绑定事实**与本批新计数行的**跨批冲突**(见下方「实施期发现」)。** - **立项形态**:批 17 已交付的 FR-5 两态定位器**绑定了「正在写的那一节」**,而本批**只加判据、不加机制面**:**在定位器绑定的区域内,令牌 `基线 N` 与 `本批 N` 的去重值集各恰 1 个**。**判据的形态是「两处推导式取值一致」,不是「令牌基数唯一」**——批 16 条目的收官实测行**刻意复述**了推导式(值一致),那是**有文档价值**的写法:按「恰 1 处」它两态皆红,而**真正雷过两次的物种是「取值不一致」**。**★ 绝不可扫非绑定节 / 全文件**:`## [0.24.0]` 与 `## [0.23.0]` 两节**今天就违反基数唯一性** ⇒ 全文件版在每个收口后态**恒红**。 - **四条负控 + 阴性对照**(**全部在纯内存夹具上构造**,另加一条**真实语料**的两态自证):① **不扫非绑定节**——夹具里两个历史节各自违反唯一性 ⇒ **腿不得报**(**四家一致点名的硬要求**);② **双推导式取值不一致 ⇒ 红**,报错含**绑定区域(态 + 行区间)与两处的 `文件:行:原文`**;③ **行内代码 span 与围栏内一律掩码不计** ⇒ 示例**绿**、去掉反引号**当场红**(真实语料:本档批 14 节里那条不一致的历史引用**就住在反引号里**);④ **边界不确定或不存在 ⇒ 判失败(红)**,**不回退全文件扫描**(fail-closed)——含**空集也红**(去重值集「恰 1 个」的字面读法)与「标记住在第二节 ⇒ 红」。 - **实现(D2 单一权威源)**:**腿只消费 `shapeLocateCountLine` 的绑定输出**,**自己不再切一次节**——定位器**加性扩展**出 `from` / `to`(绑定区域的**行区间**,0-based 半开);**既有消费者只读 `state` / `line` / `text` ⇒ 逐字零扰动**。理由:FR-5 的等值锁防的是「两个**抽取器**漂成两种方言」,而**两个定位器是同一种病高一层**。 - **零改面**:**`lib/**` 零改动**(纯测试面 ⇒ **无需重启 DSH**)· **`release-check.mjs` 零改动**(**只读**它的既有导出)· **`test/release-check.test.mjs` 零改动** · **零新增测试档**(20 档不变)· 六串零命中 · `failStop(` 恒 18 · 零新依赖 · **`docs/consult-minutes/**` 只排除不修改** · 存量 `docs/**` 零改动 · 负控**全部在仓外临时副本或内存夹具上做**(仓内零残留)。 - **★ 实施期发现(据实登记,不藏)**:**本批的新死区条目一写上,批 17 的绑定事实腿当场红在两处**——`b17-全量用例数`(docs 侧 476 vs 本批计数行 477)与 `b17-用例增量`(docs 侧 12 vs 本批 1)。**机理不是本批的腿、也不是本批的写法,而是 FR-5 绑定语义本身**:**绑定侧抽的是「CHANGELOG 当前的计数行」,而 docs 侧的注册值写死于批 17 的交付值** ⇒ **任何下一批的新条目都会撞**(批 17 当年不撞,只因为那两个值恰好就是它自己的交付值)。**⇒ 修法 = 父侧更新设计档里那两个 docs 侧值(或撤掉那两条 `bind=`)**;**它在批 18 的写域之外**(`docs/2026-09-17-scope-alignment-design.md` 属存量档,本批**禁改**)⇒ **已按 STOP 纪律上报**。 - **★ 天花板(如实登记,不许把宣言写得比机制大)**:本腿**分不清「引用的历史错值」与「断言的错值」**(§9.4 残差 #4 引文族);且**只覆盖 CHANGELOG 内**的令牌值一致(散文里的计数仍归 W1–W3 纪律与人眼)。 - **★ 收口动作(owner = 主代理,本批写域外)**:本条目**落在首个版本头之上、不新开版本头**——因为 `test/release-check.test.mjs` 的 **G-常驻2** 断言 `package.json#version` **等于 CHANGELOG 顶部版本头**,而 `package.json` 不在本批写域 ⇒ **新开版本头会让 `node --test` 立刻红**。故收口共三件——**①② 更新设计档里那两个 docs 侧注册值(否则绑定事实腿红)**、**③ bump `package.json#version` + 新开 `## [x.y.z]` 版本头并把本条目移入**。(**发布门 G3 在交付时点必然红**——它比对的是**首节**计数行而本条目在版本头之上,属死文本;与批 9/16/17 先例同形。) ## [0.25.0] — 2026-09-17 > **一句话**:文档形状谓词新增「跨文档事实等值」腿:同一个事实在多处出现时**取值必须相同**;同时修掉谓词自身的三处假红(纯测试改动,无需重启)。 **批 17(谓词作用域与宣言对齐 · **事实 id 跨档等值腿** + **三处既有正确性修复** · 本批为**纯测试面** ⇒ **无需重启 DSH**)** - **【计数行】**:`node --test` **476/476**(**基线 464 + 本批 12**)。⇒ 台账 §三 `doc-hygiene.test.mjs` 用例数 **12 → 24** 同批同步(`T-LC2` 等值锁)。**★ 交付时点实测:475 pass / 1 fail —— 唯一红在 `DOC-HYGIENE T2` 的腿 4 面(见下方「实施期发现」)。** - **立项形态**:批 16 的形状谓词**能工作**,但**只扫两处**(档内声明的锚表与 AC 表)⇒ 六次逃逸**全部落在它的作用域之外**。**判据从「某张表内部自洽」升为「同一可数事实的每一处出现彼此同值」**:事实 id 是**唯一能让机器免猜地分组**的手段(id 即「同一性」的断言本身)。**★ 会诊原议的「自识别腿全档常开」已被父侧干跑实测否掉**(腿 3 常开 ⇒ 存量档红 **34 处**裸模块名;**★ 代码评审 #7 补 as-of:34 为干跑时点的数,分歧审计复算得 26 —— 决策不受影响(26 > 0),见设计档 §12.4**)⇒ **本批增量只剩事实 id 腿**(`docs/2026-09-17-scope-alignment-design.md` §4 D17-0)。 - **三处既有正确性修复(先做,无条件)**:① **FR-0a 深度感知闭括号**(`findMatchClose`:全角 `(` 深度 +1 / `)` −1,**归零处才是本标记的闭括号**)——改前取**首个** `)` ⇒ 任何 `(§3)` 式引注都**截断列表** ⇒ 项数误判(**假红**);② **FR-0b 围栏状态机掩码**(`maskFenced` / `shapeMaskDoc`:``` 围栏内**整块**与行内 span 同待遇)——改前围栏内容**裸奔**;③ **FR-0c 掩码改「非空白占位符」**(`SHAPE_PLACE`)——改前把 span 内容掩成**空格** ⇒ **项若整体住在 span 里**(``(2 档:`a.md` · `b.md`)`` 这种**本仓必然的写法**)⇒ 段 trim 后为空 ⇒ `actual=0` ⇒ **假红**。**三条各配「改前红 / 改后绿」的负控腿**(`legacyCountMarks` 复现改前形态)。 - **事实 id 腿(本批唯一的新判据面)**:**FR-1 标记语法**(全枚举形 `(8 条 id=X:a · b · …)` **禁省略** 与裸引用形 `(8 条 id=X)`,**共用同一个匹配器**;id 字符集**排除 `@` 与竖线**——它们是 `require-facts` 的分隔符)· **FR-2 跨档分组 + 两条不变量**(① 组内标量/集合全相等 ⇒ 否则红且**报全部出现处的 `文件:行:原文`**;② **仅注册事实**:`require-facts: id@N` 的出现次数锁——**绑定出现不计入 `@N`**)· **防恒真闸**(全域 ≥1 个「≥2 处**档内**出现」的组,且**注册事实**里必须真的 ≥1 个)。**未注册的单例 id 不红**(「一次事实只出现一次」是常态)。 - **FR-3 腿 1 的双模式真值表**:**声明档保持批 16 严格语义**(`N == 项数`,零 `·` 且 N≥2 ⇒ 红——**N-3 不退化**);**未声明档**加**三条宽松约束**(零 `·` 且 N≥2 ⇒ 跳过 · 只认两侧带空格的 ` · ` · 尾随 `·` ⇒ 跳过);**带 `id=` 的标记一律走严格模式**。**FR-4 归一化 R-1…R-8**:**值只从标记载荷取**(标记外的散文永不入值)· 整数比较 · 集合 **trim 后精确串等值 && 顺序无关** · 报错报全部出现处。**元原则(D17-8):每一次归一化都是一次 fail-open 决策——归一只许修呈现,永不许修语义。** - **FR-5 CHANGELOG 覆盖(两态定位器)**:**死区**(`# Changelog` 到首个 `## [`)含**恰 1 处**那个显式定位标记(批 16 条目里已在用、**本批把它升格为定位契约**)⇒ 绑死区(**交付时点态**);否则首节含 ⇒ 绑首节(收口后态);**死区 >1 处 或 两态都不含 ⇒ 红**。**锚定行抽取**(不是正则首例匹配——那正是 `G-常驻3` 的已知雷)+ **`bind=<源>.<槽>` 三槽**(`changelog.nsN` / `.baseline` / `.added`;**畸形绑定三条各自红**)+ **等值锁**(首节态下必须 == `parseChangelogCountLine` 的输出——**只读 `release-check.mjs` 的既有导出,该档本批零改动**)。 - **FR-6 强制声明 + FR-6b 面级结构触发**:`docs/` 顶层 · 文件名匹配 `^(\d{4}-\d{2}-\d{2})-` · **日期 ≥ `CUTOFF = 2026-09-16`** · 无声明 ⇒ **红**(**CUTOFF 前 ⇒ 绿**——阴性对照证明边界不是恒红);**exempt 四闸**(独键 · 必带理由 · 只许写在日期前缀档 · **内容证伪**)+ **`EXEMPT_BUDGET`**(初值 2,**据实登记为「未实测的初值」**——它只是可见性旋钮)。**FR-6b**:已声明档内有**首列匹配 `AC-\d+` 的表** ⇒ `acs` 必填;**锚形表 ⇒ `anchors` 必填**(**「档内缩作用域」那个口的唯一堵法**)——与 exempt 闸 4 **共用同一个结构检测器**。 - **域的精确定义**:**扫描域 = `docs/**` 递归 − `docs/consult-minutes/**` + `CHANGELOG.md`**;**腿 1 全域跑**(宽松档位);**腿 2 / 腿 3 / 腿 4 / `require-facts` 只跑声明档**(严格腿);`docs/consult-minutes/**` 排除的理由**不是白名单而是范畴**(机器产物无作者契约)。**失败方向**:**闭域全 fail-closed** · **开域永久 fail-open** · **没有档能静默从闭域滑进开域**。 - **十二条新负控腿 + 阴性对照**:FR-0a/0b/0c 各一条(**含「改前红」的实证**)· FR-1 标记语法与 id 字符集 · FR-3 三条宽松约束**成对**(未声明跳过 / 声明档同构造 ⇒ 红)· FR-4 归一化 · FR-2 三条(同值 · 次数 · 防恒真)+ 集合成员 · FR-5 两态 + 等值锁反面 · 绑定三槽 + 畸形三条 · FR-6 边界三态 · FR-6b 两形 + 普通表 · exempt 四闸 + 预算。**全部在纯内存夹具 / 仓外临时档上构造**(D5:仓内零残留)。 - **★★ 实施期发现(据实登记,不藏)**:① **腿 4 的两处 id 抽取口径必须放宽为「可选一个小写后缀」**——`\bA[0-9]+\b` 在 `**A1b**` 上**匹配不到**(`1` 与 `b` 之间无词边界),`AC-[0-9]+` 在 `**AC-4b**` 上截成 `AC-4` ⇒ **假红「AC 号重复」**;而 `A1b`/`A9b`/`A11b` 与 `AC-4b`/`AC-6b`/`AC-9b` **正是本批设计档自己命名的 id** ⇒ 照旧口径**根本不可表达**。② **本批设计档一旦按 A12 带上声明(`anchors`/`acs`),腿 4 当场红在两处真实缺陷上**:**§8.2 的 A4 行 cell 数 5 ≠ 表头 4**(行内一个未转义的字面竖线 —— `含 \`@\` 或 \`|\` 的 id`)与 **A4 是孤儿锚**(§10.3 的 15 条 AC 无一条引用它,且其行无 `(豁免:…)`)。**⇒ 两条都要动设计档既有行,超出本批写域(本档只许「加声明 / 加 `require-facts` / 加事实标记」)⇒ 已 STOP 上报父侧裁定**。**⇒ 交付时点 `node --test` 有一处红(正是这两处),修法 = 改设计档 §8.2 的 A4 一行**(去掉/转义那个字面竖线,并给该行加 `(豁免:…)` 或让某条 AC 引用 A4)。 - **零改面**:**`lib/**` 零改动**(纯测试面 ⇒ **无需重启 DSH**)· **`release-check.mjs` 零改动**(本批**只读**它的既有导出)· **`test/release-check.test.mjs` 零改动** · **零新增测试档**(20 档不变)· 六串零命中 · `failStop(` 恒 18 · 零新依赖 · **`docs/consult-minutes/**` 只排除不修改** · 历史记录既有内容零改动 · 变异/负控**全部在仓外临时副本或内存夹具上做**(仓内零残留)。 - **★ 收口动作(owner = 主代理,本批写域外)**:本条目**落在首个版本头之上、不新开版本头**——因为 `test/release-check.test.mjs` 的 **G-常驻2** 断言 `package.json#version` **等于 CHANGELOG 顶部版本头**,而 `package.json` 不在本批写域 ⇒ **新开版本头会让 `node --test` 立刻红**。故收口只剩两项——**bump `package.json#version` + 新开 `## [x.y.z]` 版本头并把本条目移入**。(**发布门 G3 在交付时点必然红**——它比对的是**首节**计数行而本条目在版本头之上,属死文本;与批 9/16 先例同形。) ## [0.24.0] — 2026-09-16 > **一句话**:文档形状谓词上线:「计数不追列表」「同一事实两处取不同值」「引用了不存在的档」「该有锚的 AC 没锚」——从**靠人眼**变成**机器自己红**(纯测试改动,无需重启)。 **批 16(文档形状谓词 · 四条腿 + **八条负控构造 / 8 条顶层 `test()`** · 本批为纯测试面 ⇒ 无需重启 DSH)** - **立项形态**:把「文档形状缺陷」(**计数不追列表** · **同一事实两处取不同值** · **引用不存在** · **该有锚的 AC 没锚**)从**靠人眼 / 靠用户质问**变成**机器自己红**。**形态由两条实测否证决定**(需求档 `docs/2026-09-16-doc-shape-requirements.md` §2.3):**只认显式标记**(正则猜中文量词实测**误报 62%**)· **前向生效**(只作用于档内自带 `doc-shape` 声明的档,存量档**完全跳过、不追溯**)。**形状声明住档内、不住测试里**(D16-3:档自己声明自己怎么验,测试不做第二份真相源)。 - **四条腿**(`test/doc-hygiene.test.mjs`,与批 11 的 DOC-HYGIENE 机械网同档):① `checkCountMarks` **计数 ↔ 列表**——三条边界缺一即错:**行内代码 span 一律豁免** · **`N` 只认阿拉伯数字**(字面 `N` 不匹配)· **只数同一行内的 `·` 项**;报错文本含**档:行 + 声明值 + 实际值**。② `checkComparePair` **比对对集合相等**(**某侧读到零条目 ⇒ 红**——不得当作「空集 == 空集」通过);**只做集合型**,计数型明确不入本批。③ `checkRefs` **引用可解析**——md 相对链接恒检 · `路径:行号` 的**路径**受检而**行号不解析**(D4:行号只作 as-of)· **行内代码 span 之内一律豁免**。④ `checkAnchorsAndACs` **AC ↔ 锚互引**——**闸 A** cell 数 == 表头列数 · **闸 B** T1/T2 锚列非空(免锚的合法写法只有 `—` 与层标**连 T3 都不是**两种)· 所引锚 id 必须在锚表中有定义 · **AC 号不重复** · **反向腿(孤儿锚)**:未被任何 AC 引用的锚必须自带**显式豁免标记** `(豁免:…)`(本批设计档的 **A10** 就是那个孤儿锚,豁免语法从它这一行起生效)。 - **失败方向**:**谓词面全 fail-closed**——**扫描集为空 ⇒ 红**(断言 `scanned >= 1`;数的是**进作用域的档**而非「扫到的档」)· **锚表/AC 表定位不到 ⇒ 红**(不是跳过)· **某侧零条目 ⇒ 红** · **声明畸形(缺冒号 / 未知键 / 空值)⇒ 抛,绝不静默返空声明**;**存量档 fail-open**(无声明即跳过,**不影响任何既有档**)。**谓词只报不改**(改档是主代理 / eng_coder 的事)。 - **五条负控腿**(本批新增的顶层用例**初为这五条**:腿 1 · 腿 2 · 腿 3 · 腿 4 闸 B〔含**反向腿**〕· 腿 4 闸 A),**全部在仓外临时档 / 临时副本上构造**(D5 冻结窗口:不动被锁对象;做完即弃、仓内零残留),逐条**区分真红(谓词 / 断言级 `ShapeViolation`)与假红(`ReferenceError` / 整档崩溃)**,并各配**阳性对照**(防「恒红」的假绿)与**阴性对照**(防「恒真」的假绿)。**★ 修复轮再补第 6/7 条**(闸 0「行被吞」· F10「闸 A 后不得整行丢弃」)。**★ 代码评审 #6 修复轮再补第 8 条**(§9 空集类② 的「零数据行 ⇒ 红:双侧空表曾判绿」)⇒ **八条负控构造 / 顶层负控腿共 8 条**(反向腿并入第 4 条)。 - **【计数行】测试**:`node --test` **464/464**(**基线 456 + 本批 8**)。**⇒ 台账 §三 `doc-hygiene.test.mjs` 的用例数 4 → 12 同批同步**(`T-LC2` 等值锁:台账数字必须等于 fs 实测的顶层 `test(` 计数)。**零改面**:`lib/**` **零改动**(纯测试面)· **零新增测试档**(20 档不变)· `test/fixtures/**` 零 diff · 六串零命中 · `failStop(` 恒 18 · 零新依赖 · 历史记录既有内容零改动。 - **★ 收官实测(本行的推导式是解析器取的首例)**:批 16 顶层 `test(` **12** = 本批 8 条负控腿 + 4 条既有(T1–T4);**全量 464 = 基线 456 + 本批 8**。**批 16 内部**的分项为 5(初交付)+ 2(分歧审计 F9/F10 修复轮)+ 1(代码评审 #6 修复轮)= 8 —— **该分项不写作 `本批 N`**,因为解析器取本节首例 `本批 N`,两处写法会互相矛盾(**收口时实测命中过两次**)。 - **★ 修复轮 1(2026-09-16,独立分歧审计点名的两条谓词自身缺陷:F9 🔴 / F10 🟡)**——**只改谓词的两个入口,不重做已通过部分**,**顶层 `test(` 9 → 11**、**全量 461 → 463**、台账 §三同批同步(`T-LC2` 等值锁)。 - **★ 修复轮 2(2026-09-16,交付代码评审 #6:§9 空集类② 的 fail-open 缝)**——**`shapeTable` 的 `raw.length < 2` 判据对「表头 + 分隔线、零数据行」不充分** ⇒ **双侧空表时 `anchors`/`seen`/`referenced` 全空而整体判绿**(与 §9 失败方向①「空集 ⇒ 红」相悖;单侧空表被反向腿兜住,**双侧同空则漏**)。⇒ **补零数据行闸**(两侧各报各的、能点名哪张表空)+ **纯内存负控腿 8** + 阴性对照。**顶层 `test(` 11 → 12**、**全量 463 → 464**、台账 §三/§七同批同步。**★ 代码评审第 2 轮点名的 🔴**:本轮**漏改本 CHANGELOG**(`九条 / 8 条 / 464 / 12` 四处未同步且零留痕)⇒ **今补**——**这是本批「改两处之一不算修好」这条自订规矩的第四次复发,且四次都在我这一侧**。 - **F9(🔴,恒真入口 2.0)`shapeTable` 静默丢行**:旧实现取「第一段连续 `|` 行」,**遇到第一个非表行就 `break`** ⇒ 其后所有表行**既不入表、也不算红**(审计实测:3 行 AC 表中间插一行非表行 ⇒ 谓词返回 `PASS {"acs":2}`,**第三条 AC 完全没被看见**)。**同族第二面**:同一节关键字下若有**第二张表**,也只取第一段 ⇒ 第二张表的 AC **永不校验**。**修**:表体扫描**不再 `break`**,改记「截断位置」并**扫完整节**;截断之后若**仍有表行** ⇒ **新增闸 0**判红,消息含 **`文件:行`(被丢的行号)+ 「行被吞」+ 被丢行原文**,**且必须先于闸 A 报**(表被截断时闸 A 只看得到存活的那几行 ⇒ 被吞的行永远不会被它看见)。**关键设计约束已满足**:**「表被截断」与「表自然结束」必须区分**——判据是「**其后是否仍有表行**」,**不是**「出现非表行就红」(后者会误伤正常排版:**本仓设计档 §10.3 的 AC 表后面就跟着散文**,该档即谓词自己的阴性用例)。**补负控腿 6**(含三条阴性对照:表后接标题 / 表后接散文 / 表**之前**的裸 `|`;另覆盖**同节第二张表**与**锚表侧**)。**变异 M1 实测**:把闸 0 退回「一遇非表行即 break」⇒ 该腿在**审计原始构型那一条断言**上 `Missing expected exception` **真红**,两条阴性对照仍绿。 - **F10(🟡)闸 A 报红后 `continue` 致整行丢弃**:旧实现给 cell 数不符的行直接 `continue` ⇒ **其 AC 号不进 `seen`、其锚引用不进 `referenced`**(审计:一个**真实存在**的 AC 因此触发**反向腿的二次误报**)。**修**:AC 号与「**该行实际出现的锚引用**」**先记录**,再对残缺行**只跳锚列判定**(层标分层不变量),**不跳 id 记录与引用记录**。**补负控腿 7**(含 F10 两条下游误报的可观察面 + 反向腿的**结构性边界** + 两条零回归对照)。 - **★ 本轮的诚实自白(写进设计档 §11/§12.2,不藏)**:**F10 声称的两条下游误报(孤儿锚 / AC 号重复)在可构造的形态下均不可观察**——① 残缺行若**丢掉锚列**,谓词**本来就看不见那个引用**(**结构性**:谓词读不到的东西不可能被它看见,不因本次修复而变);② 残缺行若**首列即 AC 号**,后续行的 AC 号取自**本行首列**(`cells[0]`),**不可能**被误判为重复。**⇒ 负控腿 7 据实钉的是「残缺失效行的任何东西都不得丢」(`referenced` 零丢失 + 反向腿照旧会报 + 两条零回归),而不是钉一条构造不出来的二次误报**;`continue` 的代码缺陷(整行丢弃)本身**确实存在且已修**,只是它的下游症状在本谓词的形态下有更强的守卫(闸 A 先报 + 锚表独立解析)。 - **文档与登记面同批对齐(改一处即改全部)**:设计档 **§9 空集类补为三个入口**(**零档 / 零行 / 行被吞**)· **§6 伪代码 ⓪/⑥ 两条 `@post`** 与实现同值 · **§8.2 负控表补第 7/8 行**并把口径统一为「**八条构造 / 7 条顶层 `test()`**」· **A7/A10** 与 **AC-6/AC-8/AC-10/AC-11** 四处同改(**基线 + 5 → 基线 + 7**)· **§11 变更记录**与**§12.2 处置表**(F9/F10 由 `Dispatched` 结清为 `Fixed`)· 需求档 **US-4** 与状态行 · 本台账 §三(`doc-hygiene.test.mjs` **9 → 11**)。 - **零改面**:`lib/**` 零 diff(纯测试面)· `test/fixtures/**` 零 diff · **零新增测试档**(20 档不变)· 六串零命中 · `failStop(` 恒 18 · 零新依赖 · **禁改面零触碰**(`test/stage-gate.test.mjs` · `test/stages.test.mjs` · `test/guard-e.test.mjs` · 平台包 · `docs/` 顶层既有纪要 · 历史记录既有内容)· 变异/负控**全部在仓外临时副本或内存夹具上做**(仓内零残留)。 - **★ 收口动作(owner = 主代理,本批写域外)**:本条目**落在首个版本头之上、不新开 `## [0.24.0]`**——因为 `test/release-check.test.mjs` 的 **G-常驻2** 断言 `package.json#version` **等于 CHANGELOG 顶部版本头**,而 `package.json` 不在本批写域 ⇒ **新开版本头会让 `node --test` 立刻红**。**★ 代码评审 #3 订正**:顶部计数行**已随修复轮改为 `463/463`**;**★ 代码评审第 2 轮再订正**:**终值为 `464/464`**(代码评审 #6 修复轮再 +1,见上行「测试」条)。故收口只剩两项——**bump `package.json#version` + 新开 `## [x.y.z]` 版本头并把本条目移入**。(**发布门 G3 在交付时点必然红**——它比对的是**首节**计数行而本条目在版本头之上,属死文本;与批 9 先例同形。) ## [0.23.0] — 2026-09-16 > **一句话**:配置面与描述面同步:`engCoderEffort` 默认值由 `low` 改为 `medium`(`low` 是唯一可能被静默降成「推理全关」的档);白名单散文立了常设谓词;清掉十处悬空引用。 **批 14:配置面与描述面同步 —— 「插件自己的契约」与「它对外声称的契约」的一致性;六项里五项改散文或注释、一项改行为(默认值)⇒ 风险不是逻辑错,而是「改漏一处」** - **★ 立项形态**:本批**六项里有五项是描述面同步**,故**§8.2 的机验锚(A1–A12,逐锚写全「检索目标 / 谓词 / 期望」)才是核心交付物**,不是附属品(会诊 R-9 原话:「§8 若只列人工检查项,本批等于自证失败」)。**一切改动只准字符串锚定、禁行号锚定**——会诊实测行号在本批写域内**已漂三处**(N-1)。 - **FR-1 默认值三处同源(`lib/eng.mjs`)**:抽 `ENG_CODER_EFFORT_DEFAULT = "medium"`(镜像既有 `ENG_CODER_MAX_TOKENS_DEFAULT` 的形态),初值引用它、`else` 分支**显式赋值**它、警告文案随之 ⇒ **P2 的「回落 = 初值」隐式耦合被消除**(`grep 'falling back to'` 的文案现在指向常量值,不再写死字面)。**改值的枚举依据**(会诊 v4-pro 提供、父侧复核形式):`low` 是**唯一在一切非退化支持集上都可能被 `nearestEffort` 静默落到 `off`** 的程度档——而**本部署实测形状 `{off,high,max}` 恰是它中招的形状**,`medium` 只在退化集 `{off}` 落 `off`;出厂示例(`README` + `cordis.patch.yml`)此前都写 `low` ⇒ **新部署照抄即中招**。**否决「保留 `low` + 只显式化」**:显式化对、但不解决 P1(两家论据都采纳)。**`nearestEffort` 的根修另立批次**(四机制共享面,D14-2)。 - **FR-4 到点文案 + 前置告警(同档)**:三处第一字面统一改为 `超内部截止(内部截止值取自 codexCli.budgetCapMs=)`——**键名补节前缀**(schema 里**没有**顶层 `budgetCapMs`,真键在 `codexCli` 节下 ⇒ 用户在顶层配一个**自然无效**,那正是被引偏的机理);**第二字面一字不动**(`doc-hygiene.test.mjs:317` 的 needle 期望 **2 次**,它就是这对竞态分支——reject-race 与 resolve-race——的既有锁,**不可合并**)。新增 `PLATFORM_WALL_CLOCK_MS = 600_000`(**平台事实常数,不新增配置键**)+ **前置告警**:**仅 dsh 同步路径**、`budgetCap >= 600000` 时 **spawn 前** warn 一次;**`background=true` 走后台时不告警**(后台无平台墙钟问题,告警即错告,D14-6);**不引入 `failStop(`**(计数锁恒 18)。 - **FR-2 白名单散文谓词(`test/config-api.test.mjs` 的 U3c2b)**:权威面 = **`TOP` 8 · `ADV_SUB` 6 · `GROUPS` 2 · `GROUP_FORM` 5 = 21 名**(`topAllowed` ×8 · advisor 子键 ×6〔`round1`/`convergence` 另归组键轴〕· 组字段 ×5)——**★ 本条初写「三轴 8/8/5」是父侧把「8」贴错了轴(分歧审计 F2 实测订正;恒等式 8+6+2+5=21 一直成立)**,散文面 = `cordis.patch.yml` 与 `README.md` 的**用户层白名单段** ⇒ **逐文件各一条断言 + 先钉基数 + 双向比对**(散文 ⊆ 权威 ∧ 权威 ⊆ 散文)。提取器**两级过滤**,每一级都由一个**实测陷阱**逼出来:段级切「user 层可配」段(尾部还有一句以「只在 base 配」起头的排除句)· EOL 归一(`README.md` 是 CRLF、`patch.yml` 是 LF)· 行级先切排除子句(`;`/`——`)· 标记剥离 · 首字符小写 + 长度 ≥3(滤 `D-29`/`R1`/`FR-CB5`)· stoplist。**带三条自证腿**(AC-20):**腿 A = 吞掉标记剥离 ⇒ 多抓**(由 `keys.size > …` 接住)· **腿 C = 构造真空集 ⇒ `assertDocKeysNonEmpty` 必红**(★ 由补腿轮补入:**「空集 = 恒真的唯一入口」此前只是声明、不是已验证的性质**——分歧审计点名)· **从散文删一个键名 ⇒ 「缺项」断言必红**。 - **★ 因果对(D14-7)**:`lib/config-store.mjs` 的 `mergeGlobalConfig` 头注释**补 `runner`**(P4 —— **本批唯一的现役缺陷,只有会诊看到,摸底与独立勘察都漏**):该处只列四名而 `GROUP_FIELDS` 是五名 ⇒ **谓词会一直红到它补上为止**,故两者**同一 stage** 交付(先出谓词必然红,而那个红会与「其他项未完成」的红混淆)。 - **FR-3 锚 A 的两分支各打各的准确话(D14-9)**:批 13 审计 F8 的遗留——ENOENT(无 git)与 128+not-a-repo **都**留空 `addingSha` ⇒ **都**落「锚 A 未激活:本批新增档尚未提交」,而**在无 git 的机器上那句话是无意义的**(它根本不是提交问题)。修法:取数与报告抽成 `anchorAProvenance` / `reportAnchorA`,**两个分支都置旗标** `notRepo`;**ENOENT 打新写的准确话**(「无 git ⇒ 锚 A 不可判定」)、**128 维持既有的准确话**、**`else` 只在两分支都未命中时触发**(那时 git 可用而 `addingSha` 取不到 ⇒ 那句才是真的)。新增 **T-AP9b** 走真子进程验三形态 + 一条**正控**(证明那句 `else` 没被误关)。 - **FR-5 悬空引用清尸(10 处 / 7 档)**:`DESIGN-dsh-port.md` 7 处 + `DESIGN-advisor-token-protocol-fix.md` 3 处,**盘上零命中、git 历史零命中——只有引用没有正文**。修法 = **只删指针 + 连接词手术,零新增散文、不重指、不新建档**:**重指 `docs/2026-09-13-portability-design.md` 会制造新假指针**(那档主题不对,是批 7 的「产品提示词去本仓指涉」档)⇒ 否决父侧原候选(D14-4)。分布:`eng.mjs` **3** · `advisor-msgs.mjs` **2** · `advisor.mjs`/`consult.mjs`/`index.mjs`/`readonly-tools.mjs`/`state.mjs` 各 1。 - **散文面(D14-10 / D14-11 / AC-24 / 锚 A12)**:`README.md` 的示例值与「缺省」字样 + **理据句**(原句「低推理档把输出预算留给正文」**以 `low` 为前提、改默认值后即成假**)· `cordis.patch.yml:43` 的示例值——**它就是诱捕**:只改 README 不改 patch.yml ⇒ **下一个照抄 patch.yml 的人照样中招**(本批物种的第二次犯罪)。**AC-24 是常设锁,不是 stage 目标。** - **FR-0 授权门哨(最先做)**:`test/advisor-config.test.mjs` 在基线集内且不在 `AP_TEST_AUTHORIZED` ⇒ 先读该行、**断言含 `test/consult.test.mjs`**(批 15 交付的门哨;缺失即停),再**追加**本档名(**不重写整行**)+ 注释块追加第 ⑤ 条授权理由 ⇒ 数组 5 名。 - **锁面变更(须显式声明,N-3)**:改 `test/advisor-config.test.mjs`(默认值锁两处 + 到点文案/前置告警)与 `test/config-api.test.mjs`(谓词)⇒ 两档均走 `AP_TEST_AUTHORIZED` 授权仪式。**零改面**:`test/fixtures/**` 零触碰 · **不新增测试档**(20 档不变)· 平台包 `dsh-tool-jobs` 零改动 · `docs/` 顶层既有纪要零改动 · 历史记录既有内容零改动(`CHANGELOG.md` `:215` 那条原行不动)· 六串零命中 · `failStop(` 恒 18 · **第二字面仍 2 次** · 零新依赖 · 零删测试档。 - **测试**:`node --test` **464/464**(**基线 456 + 本批 8**)。**★ 本行是发布门 G3 与 `G-常驻3` 读取的「首节计数行」**——**它的推导式由解析器从本节取首例**(`release-check.mjs:59-71`:本节第一个 `**N/N**` · 本节第一个 `基线 X` · 本节第一个 `本批 Y`)⇒ **本节只留这一处推导式**。 - **★ 上一节(v0.23.0)的计数行历史留痕**:那次交付时点为 `456/456`(当时写作 `基线 453 + 本批 3`)——**该分项本轮已移出本行**,因为解析器会把它当成本节的推导式(**收口时实测命中:取「基线 456」+「本批 5」⇒ 461 ≠ 464 ⇒ `G-常驻3` 红**)。**这是本批「同一事实两处取不同值」的第五次复发,且这次是解析器抓的。** - **★ 与「台账 §三零改」的口径冲突如实登记**(批 14 的既有记录):新增顶层 `test(` 必撞 **T-LC2**(该档 §三的逐档用例数与 fs 实测是**等值锁**——`docs/test-lifecycle.md:158` 已把它登记为「第三把元锁」)⇒ 批 14 **同时更新该档三行的用例数**(`advisor-config` 59→60 · `config-api` 24→25 · `death-provenance` 18→19)并写明理由;`test/test-lifecycle.test.mjs` **零改动**(禁改档)。**若不接受此偏离,替代路径 = 把三个新用例的断言并入既有块**(批 9 收尾轮先例,代价是粒度损失)。 - **D14-12 是核验、非改动**:用户裁定③(`goal`/`authorized` 如实降 T3)的落点 = 批 15 的两档;实测 `docs/2026-09-15-consult-delivery-design.md` §11.2/§11.3 的 **AC-5 / AC-25 已是**「T1(仅 `unattended`)· T3(`goal`/`authorized`)」⇒ **本 stage 为空操作**(不按过期前提改错面)。 - **本批不可真机验证(如实登记)**:`lib/**` 改动**需重启 DSH 才生效** · `console.warn` 的即时可见性 · 仓外部署 profile 的 `cordis.patch.yml` 同步(owner = 用户/运维,本批只登记漂移、不替它改)。 - **★★ 修复轮(2026-09-16:独立分歧审计点名的**唯一代码面缺口** —— 「空集 ⇒ 必红」从未被任何腿触发)**:设计档 §8.2 的 A4 负控承诺「把提取器**故意改坏** ⇒ **空集 ⇒ 必红**」(§6.2 图 2 的 `assert 非空`:**空集 = 恒真的唯一入口**),**而交付里没有任何一条腿把提取集弄成空集**——当时的两条腿实测走的是「喂整档 ⇒ **多抓**」(`sabotagedWhole.keys.size > …`)与「删键 ⇒ 缺项」,**唯一防恒真的那道护栏 `keys.size >= 1` 自身从未被证明会红** ⇒ 那句承诺当时只是**声明**、不是已验证的性质(**设计档 §8.1#3 订正后的边界正是本轮的适用口径**:禁的是新增**测试档/测试层**,在**既有档内增用例是允许的**,条件是台账同批同步)。**修法两件**:① 那道护栏抽成**单点实现** `assertDocKeysNonEmpty(keys, what)`(⓵⓶⓷ 三处改调它 ⇒ 负控证的就是三处**实际调用的那一个函数**,而不是一句会与 ⓵ 漂离的复制品);② 在既有 **U3c2b** 块内补 **腿 C**——构造 ① **段级失配 ⇒ 块为空**(真实档字节不动、只把段起标记改坏 ⇒ 段级切分返回 `null`)⇒ **提取器入口的非空断言必红**(fail-closed:空块不得静默当空集放过);构造 ② **块非空但零键名**(把真实白名单段内的拉丁 token 机械抹平)⇒ 提取器**返回** `keys.size === 0` ⇒ **对这条空集复跑护栏 ⇒ 必红**;另附**阳性对照**(同一护栏对非空集不得抛,否证「腿 C 恒红」的假绿)。**变异自证三条**(逐条 byte-exact 回退 + sha256 复核,全为**真红**、无假红):**M1a** 把新腿的构造改回非空 ⇒ 红在「构造自证②」(实测抓到 11 名 ⇒ 证明红的是**空集**这条性质);**M1b** 把护栏的 `>= 1` 改成 `>= 0` ⇒ 红在 `assert.throws`(**Missing expected exception** ⇒ 证明腿 C 绑的是**护栏自身**,不只是它自己的构造);**M2** 删 `config-store:19` 的 `runner` ⇒ 红在 A5/AC-7 那条。**零改面**:**零新增测试档**(20 档不变)· **零新增顶层用例**(`config-api` 用例数仍 **25** ⇒ 台账 §三的计数单元格不动、T-LC2 等值锁不受影响;仅同批改写该行的**存续理由**把腿 C 登记进去)· `lib/**` **零 diff**(30 档 sha256 全等)· `test/fixtures/**` 零 diff · 六串零命中 · `failStop(` 恒 18 · 零新依赖。 ## [0.22.0] — 2026-09-15 > **一句话**:会诊接上平台后台投递(完成时**自动通知你**,不用轮询)、退役旧轮询工具、纪要**先落盘再通知**、未消化的会诊会拦住下一次发起。 **批 15:会诊结果的投递与消化 —— consult 接上平台 job 投递、退役轮询工具、digest 单消费面 + 消化门禁;**补的是本仓落后上游六天的那一段** - **★ 立项事实(本批的第一件事)**:上游 `3e1234b`(2026-09-07 04:10)已以 **digest 自动注入退役 `consult_check`**,而本仓复制源是 **`3e1234b^`**、首个提交 `aeffdf7`(2026-09-01)⇒ **本仓抄的是改造前的祖先,六天里从未记录这次分叉**(唯一痕迹 = `:215` 那行还在对上游已删协议做加固)。本批把这段补上;**「停下汇报 / 三豁免档 / 纪要默认落档」三件是本仓走在上游前面的加法**。 - **FR-1 派发与投递**:`consult_start` 不再 fire-and-forget,改为 `jobs.start({ kind: "consult", label, owner, outputLimitBytes: 131072, run })`——**逐字段对齐 `lib/eng.mjs` 的 `kind: "eng-dsh"` 先例**,取服务复用 `lib/advisor.mjs` 的 `getJobsService`;`run` 体内起 N 个只读子代理并 **await 全 settle**(含早停)。`composeConsultDigest` 抽为**纯函数**(头部行 `[consult #id finished|stopped — N of M replied (F failed[, S stopped])]` + 逐条回复 + **有效数段**)。**★ 落盘次序是本批的兜底机制**:settle 时**先写纪要原始层,再让 job complete** ⇒ **投递成不成功,纪要都在盘上**(失败方向:投递面损失只许是「汇报」,不许是「记录」)。 - **FR-2 退役 `consult_check`**:删注册点 + 删 `checkConsultSession` 与其 `waiters` 机制(**18 个字符串点 / 26 个调用点**分面处置;`lib/**` · `lib/prompts/**` · `README.md` **零命中**);`consult_start`/`consult_stop` 描述同步改;eng/escalate 的 `toolFilter` deny 名单**删名**;`test/death-provenance.test.mjs` 的 **T-AP1d 改指 digest**(消费面换人后,批 6「死亡行必须经**生产消费面**可见、不得手搓等价物」的纪律零削弱)。**★ 并逐字反转**批 14 A 项那句「There is NO completion notification … you must come back yourself」——本批让 consult 有通知 ⇒ 那句变假;`37d7eef` 的历史**不重写**,只在设计档 §10 偏离表与本次 CHANGELOG 留痕。 - **FR-3 消化纪律与门禁**:缺省 = **停下向用户汇报**;三豁免档 = 「无人值守」(**仅此档在 start 声明**,`note` 必填)/「明确目标」/「明确授权」(后两者**在送达时判**,落纪要裁定层 + ack——**三处机制留痕都在 settle 前合成,送达时判定写不进它们**);**门禁不变量** = 不存在 `settled ∧ ¬digested ∧ ¬minutesExempt` 的会话 ⇒ 命中即 **拒发 + 内联未消化 digest + ack 指引**(**拒发即恢复通道**);**台账孤儿扫描**(有 `started` 无 `settled`/`stopped`/`disposed` ⇒ **建议性提示、不阻断**,且在飞会话不误报);`lib/prompts/main.md` 重写(含「wakeup 回合**先 `job_output` 读全文**,再处置」——完成通知只含一行指针)。 - **★ 两处有意偏离**:① **`stopped` 会话照产「墓碑 digest」**(偏离上游 `T-R17c`):退役轮询面后 digest 是**唯一**消费通道,stop 无 digest = 已收到的回复整批蒸发 + 死亡行失去消费面(**直接打穿批 6 的死亡行裁定**);② **`jobs` 缺失 ⇒ 拒发(fail-closed)**(偏离仓内「响亮告警 + 回落同步」先例):先例前提是工作量适配平台 600s 墙钟,而 consult 的预算(`consultTimeoutMs`,**缺省 600000ms**)远超它 ⇒ **回落路径自身就违约通知保证**,且 consult 是 N 路并行。**另登记两处**:`kind: "consult"` **不带路由后缀**(会话混跑 dsh 子代理与 codex-cli 行,带后缀必有一侧说谎)· **不做 `escapeXml`**(DSH 的 `job_output` 是纯文本、无 XML 信封)。 - **★ 纪要默认落档(本仓加法)**:`docs/consult-minutes/-consult--minutes.md`——**机制只写 §0 汇总 + §1 原始层**(有回复超 **8000 字软顶**时补 **§1.1 未截断全文**),**§2–§5 裁定层由主代理写**(**插件永不写纪要的裁定层**,这是「默认落档」能落成机制的关键)。**命名按 `` 派生而非 ``**:settle 时刻题目不可知 ⇒ 机制必须能**确定地**命名。 - **台账(新增)**:`$DSH_HOME/.thincoder/consult-ledger.jsonl`,**append-only**(`started`/`settled`/`stopped`/`exempted`/`digested`/`disposed`);**写失败仅 warn、不阻断投递**(取证面不承重);读者**前向兼容**(未知 `ev` 忽略、不报错);并兼任 **id 计数器续接**的事实源(重启后不复用 id ⇒ 纪要档名不碰撞)。 - **零改面**:`lib/advisor.mjs` 零 diff · `test/fixtures/**` 零触碰 · 平台包 `dsh-tool-jobs` 零改动(在仓外,本批只接上去)· **不新增测试档、不新增顶层 `test(`** ⇒ 台账 §三零改 · 六串零命中 · `failStop(` 恒 18 · 零新依赖 · 本档 `:215` 那条既有行**原样不动**(历史记录不是消费点)。 - **测试**:`node --test` **453/453**(**基线 453 + 本批 0**——新面的断言**并入既有块**,顶层 `test(` 计数不变,T-LC2 逐档用例数不变)。**锁面变更(须显式声明)**:`test/consult.test.mjs` 在基线集内且不在授权面 ⇒ 走**授权仪式**(`AP_TEST_AUTHORIZED` 逐档登记 + 裁定引用写进同处注释;**本批零退役** ⇒ 台账 §四 保持为空——**授权通道 ≠ 退役登记**)。 - **本批不可真机验证**:投递真的会到(空闲开回合 / 忙时注下一步)· 「停下汇报」的实际体验与质量(**质量只能人眼**)· 纪要落档的实际形态。**本批触碰 `lib/**` ⇒ 需重启 DSH 才生效。** - **★ 修复轮(2026-09-16:独立分歧审计 F1/F14 + AC partial 的处置)**:**F1(🔴)纪要落点改子目录** —— 机制落点由 `docs/-consult--minutes.md` 改为 **`docs/consult-minutes/-consult--minutes.md`**(用户裁定;`test/doc-hygiene.test.mjs:122` 明文「域 = `docs/` 顶层:子目录天然在域外」)。**原落点是双侧实跑可证的陷阱**:写侧落顶层,而检查侧的 R-25 登记谓词 `docsTopLevel()` **非递归**只读顶层、且要求顶层每个 `*.md` 登记在 `docs/README.md` 全文里 ⇒ **真跑一次会诊就会让套件红**;又因各测试档注入临时 cwd ⇒ **陷阱只在生产触发**(代码侧零缓解)。**F14(🟡)悬空指针改自含**:`test/death-provenance.test.mjs` 的 ④ 条原写「见 `docs/test-lifecycle.md` §一」,而 §一 是**通用三层判据表、没有 consult 行** ⇒ 改为**内联理由 + 指向真实落点**(§三 的 `consult.test.mjs` 行),并同物种订正 `:984` 的授权通道指针 `§一 → §二「退役的合法路径」`;**`docs/test-lifecycle.md` §三 的存续理由真的改写**(「锁会诊跨回合存活」⇒ **平台 job 投递 + digest 单消费面**下的跨回合存活与有界终止)。**AC partial 处置**:**AC-5 / AC-25 的 `goal`·`authorized` 两档如实降为 T3**(只有提示词承载、零机制——**不发明新机制**,设计档 §11.2 同批同步)· **AC-7 形状腿补成可机检**(机制产的纪要必须给每条回复**恰一个编号槽位** `[N]` 密排 `1..N` =「处置行数 == 回复数」的形状前提)· **AC-16 正向锁真的加进测试**(`Delivery is AUTOMATIC` / `STOP and report to the user`;反向:批 14 那句只准住注释)· **AC-20 的 `alreadySettled` 错误用例补测** · **AC-23 / US-12 真实覆盖**。 - **修复轮的测试与零改面**:**零新增测试档 · 零新增顶层 `test(`**(全部并入既有块 ⇒ 顶层计数不变、T-LC2 逐档用例数不变、台账 §三零改)· 全量 `node --test` **相对基线零新增用例且全绿**(453 例)· 锚 V1–V10 与零改面逐条复跑 · 设计档 §7 ④ / §8.2 V8 / §11.2 分层表 / §11.3 逐条同批同步。**★ 已知残余(父侧裁量)**:纪要落点的**字面路径**在 `lib/prompts/main.md` 与 `README.md` 仍写旧形态(**描述面与实现不一致**——digest 自报的 `minutesPath` 才是权威且 ack 走 fs 存在性校验;两档不在本修复轮的逐 stage 写域内,故**只登记不改**)。**⇒ 已由下方「收尾修复轮」闭合**:两档订正为子目录形态 + AC-16 锁组加**路径正向锁**。 - **★★ 收尾修复轮(2026-09-16:交付代码评审轮次 1 的两条实质发现 —— job `advisor-dsh-2`,`VERDICT: PASS`,🔴0 · 🟡4 · 🔵3)**:两条都**长在修复轮 1 自己新增的代码里**(分歧审计看不到——它们随修复轮 1 才进来)。**#1 看门狗泄漏**:`lib/consult.mjs` 的 **stop-竞速早退分支**在 `setTimeout(watchdog)` **之后**才判 `session.stopped`,却**漏 `clearTimeout(watchdog)`** ⇒ 定时器 + 已 settle 控制器**滞留至 `consultTimeoutMs`(缺省 600000ms)**。**★ 而这不是新物种**——同文件 codex 行 `finally` 的注释(advisor 🔵#2 的产物)**逐字记录过它**(「看门狗必须在这里显式清掉,否则 unref 定时器 + 已 settle 控制器滞留至 timeoutMs」)⇒ **修复轮 1 新增的早退复刻了自己刚修过的 bug**。**#2 终结事件漏 `sessionId`**:三类终结事件(派发拒发的 `disposed` · 失败信封的 `settled` · `stopped`)都不带 `sessionId`,而孤儿扫描 `consultLedgerOrphans` **按 `sessionId` 过滤本会话的行** ⇒ 它们被**系统性丢弃** ⇒ **永远关不掉自己的 `started` 行**。**可达后果**:`jobs.start` 抛错被拒发后(如撞平台 per-owner job 上限),该会话**此后每次 `consult_start` 都附加一句幽灵提示**「#N …… 未见 settle——可能因重启丢失」——**对一个实际被拒发、且已落 `disposed` 行的会诊,这是误导性陈述**。**修法取「写侧补 `sessionId`」**(不取扫描侧 id 级关闭),三条理由:① `sessionId` 本就是台账行 schema 的一部分(设计档 §7 ⑤),且同文件 `started` / **正常** `settled` 写点都带着它 ⇒ 缺它的三处是**写侧自相矛盾**,不是读侧策略;② 台账是 **append-only 取证工件** ⇒ 读侧打补丁把**错的字节永久留在盘上**(人眼与未来读者读到的仍是缺字段的行),写侧修才让工件自描述;③ 读侧「id 级关闭」会让**其它会话**的同 id 终结行关掉本会话的 `started` 行(今日靠 `ledgerMaxId` 全局续接侥幸安全)⇒ 等于**悄悄削弱那条专防跨会话污染的过滤**。 - **收尾修复轮的描述面与版本记事(评审 #3 / #5)**:**#3**`lib/prompts/main.md:30` 与 `README.md:146` 仍写**旧顶层落点** `docs/-consult--minutes.md`,而 F1 修复后真实落点是 **`docs/consult-minutes/…`** ⇒ **主提示词是模型可见的指令面,会把 wakeup 回合引向错路径**:两处就地订正,并按评审追加要求**在 AC-16 锁组加路径字面正向锁**(断言两档含 `docs/consult-minutes/` **且**不含旧顶层字面——带谓词自证);**#5** 本条目内两处路径形态不一致(上面 `:14` 的旧顶层 vs 修复轮注记的已订正形态)⇒ **就地订正**(本条目是本批自己的交付物、发布前可改,与「历史行不动」不冲突)。**测试与零改面**:新面全部**并入既有块**(**零新增测试档 · 零新增顶层 `test(`**)⇒ 顶层用例计数与台账 §三零改、T-LC2 逐档用例数不变;两条实质修各带**自证腿**(删 `clearTimeout` 的变异 ⇒ 点名该腿必红;把 `disposed` 行改回无 `sessionId` 形态 ⇒ 幽灵提示**必现**的对照腿在场内,且断言走**生产站点**=`apply()` 注册的 `consult_start` 工具 `execute`);另加一条**写侧静态锁**(四个终结事件写点逐一断言带 `sessionId`——覆盖「失败信封」这条现有桩下不可达的写点)。**两条 Deferred 登记在案不修**:**ack 弱绑定**(`minutesPath` 只做 fs 存在性校验,不绑内容——要强绑须动 ack 面语义=设计变更,超出修复轮写域)· **`README.md:148` 陈旧机制描述**(仍写 `main_history` 工具回看历史,而该工具在本仓已退役、改为主历史尾部直接注入——与 X-1 同物种,登记待下一批与 X-1 一并处置)。 ## [0.21.0] — 2026-09-15 > **一句话**:七条老登记逐条回盘核实——**三条描述本身就写错了**、两条范围比登记大得多、一条比登记更便宜。 **批 13:登记面与判据面余项 —— 七条登记回盘 + D-38 清偿;其中三条登记描述本身是错的,而「枚举/规格不追列表」这个病在四层评审里各被抓住一次** - **本批的第一件事不是写代码,是把七条老登记逐条回盘核**(登记表是**快照**,它的家只有人眼闸 G7)⇒ **三条描述是错的、两条范围比登记大得多、一条比登记更便宜**: - **O-E5 说反了**:登记写「评审传相对而写传绝对 ⇒ 预闸 miss」。**实测不成立**——`normalizeDocPath` 的 `resolve()` 本就把相对转绝对,三种形态归一为同一串,两向查找都命中。**真实的是基分叉下的错根锚定**:基错时 **hash 侧 fail-closed**(不绑文档集 ⇒ 续期恒拒,可用性损伤、方向安全)而 **冻结侧 fail-open**(冻结集锚在错根 ⇒ 预闸对真实绝对路径写**静默放行** ⇒ **在飞窗口的文档冻结失效 = 保护性损伤**)⇒ **比登记的「miss」更坏**。 - **R-9 是 4 项 / 2 个文件**(不是 1 键 1 文件):缺 `advisor.contextTokens`(批 5)· `advisor.standardsDoc` + `advisor.documentMapDoc`(批 7)· **组字段 `runner`**(**早于批 5**);且 **`README.md` 有同构的第二份清单**(登记完全没提)。 - **R-4a 是 3 处同类**(不是 1 处):`death-provenance` 的 T-AP7 与 T-AP9 锚 A + `design-review-guard` 的 T-G9 ⇒ 真实严重度 = 「**没有 `.git` 的副本里整份套件部分转红**」。 - **外加父侧自己的简报也有一条错的**(「6 维句 design 与 code 都发」)——**四家会诊模型全部独立指出**,源码裁决确认它们对。⇒ **「登记文本订正」被列为与代码修订同级的交付物。** - **R-5 判据补齐**:设计评审的系统提示列 **7 维**(含 **Document ownership**),而 design 专属的用户消息句只列 **6 维** ⇒ **原地追加第 7 维**(**无条件**——该句本就在 `reviewType === "design"` 块内,**分支本身就是那个条件**),并加**负向锚**把「**design-only**」这个边界本身锁死(code 评审消息**不得**含该子句)。 - **R-6 键化**:code 评审的判据档从 `/.thincoder/advisor.md`(**上游产品名当目标仓约定路径**)改为可由**独立声明键 `advisor.criteriaDoc`** 指定,**六面接通**(服务端白名单 + PUT 校验 + merge 白名单 + 生效下探 + 消费面 + 客户端 UI)且 **`loadAdvisorMd` 升三态**(声明可读 ⇒ 注入 / **声明不可读 ⇒ 响亮句点名该键** / 未声明 ⇒ **legacy 链逐字节保持今日行为**)⇒ **对存量用户零行为变更**(用户裁定:遗留链**保留并标 legacy**,退役另立批次)。**严禁并入 `standardsDoc` 家族**——两者语义不同域(一个是 code 判据**正文**、一个是两面都注入的**追加段**),并键会**静默替换**声明了标准档的仓的 code 判据。 - **R-4a 收紧(★ 本批最重的一次自纠)**:初版容忍条件只看 **`status === 128`**。**父侧与独立审计各自实测证实**:`git show HEAD:` 在**两种**情形下都退 128 —— ① 不在仓库内(stderr = `fatal: not a git repository …`)· ② **在仓库内但该 path 在该 revision 不存在**(stderr = `fatal: path '…' does not exist in 'HEAD'`)。⇒ **只看 status 会把 ② 这种真回归(文件被改名/删除)从「红」降级成「静默 warn」——比本条要治的原问题更糟**。**修法**:同时匹配 stderr;**`bad revision` 与 `path does not exist` 两种 128 仍 `throw`**。**审计比父侧更准的一条**:**只有 T-AP7 使用 `HEAD:` 语法**是唯一暴露于该危险路径的,另两处的 git 语法对不存在的 path **返 exit 0**。 - **R-25 登记机检**:仓规「新建文档必须先登记到本地图」**此前无机检**(纯人眼闸)⇒ 落一个**并入既有块**的谓词(**不新增顶层 `test(`** ⇒ 台账零改):域 = `docs/` 顶层 `*.md`(**非递归**)· 例外**恰一条**(`README.md` 自身)· 断言 = 域内每个 basename 必在 README **全文**的 `](name.md)` 链接里(**只认纯文件名形态**)· **带四腿自证**(合成未登记名 ⇒ 必红;空登记簿 ⇒ 必红)。**落地即绿、零追加豁免**——**而它建的当晚就抓住了本批自己**:我提交那三档时漏登记,V8 当场报红。 - **D-38 清偿**:`lib/eng.mjs` 两处陈旧活句(`:52` 的「逐字节锁着」→「**可执行行**逐字节」;`:471-472` 的「`stage-gate` 逐字节锁该切片」→ **该切片受四标识符禁用检查覆盖,逐字节锁区间 `[makeWriteGate, WG_END)` 从切片结束处才开始、两者不重叠**,并点名真正约束 helper 位置的活断言)。**注释 only**;且实测**修它无需重基线夹具**(两处都在字节锁区间之外)。 - **R-4b**:删 `deathLine` 循环后的**不可达 `return`**(`|| dLen === 0` 使末轮必返)——**行为逐字节不变**。 - **R-8**:`lib/index.mjs` 的 `check` 示例去本仓形状。 - **零改面**:`lib/**` **只有注释改动**(`eng.mjs`)· `test/fixtures/**` 零触碰 · **批 6 那十档基线档里只有 `design-review-guard.test.mjs` 被改**(**已在 `AP_TEST_AUTHORIZED`**)· **不新增测试档、不新增顶层 `test(`** ⇒ 台账 §三零改 · 六串零命中 · `failStop(` 恒 18 · **453/453**。 - **★ 本批的元教训:「枚举/规格不追列表」这个病,四层评审各抓住一次**:① **设计评审 #1** 抓到 G9 的验收面漏 AC-22;② **独立审计 F3** 证伪 §9 边界 10 的「`PK_KEYS` 加一键 ⇒ T-PK14 家族四面自动扩」(T-PK14d **不吃 `PK_KEYS`**,而它是 **AC-13 的唯一覆盖点** ⇒ 第三键的 merge 行为**零测试**);③ **代码复评 #1 = `VERDICT: FAIL`**——收紧后的谓词有**四处副本**没同步(含**父侧自己的需求档 US-3**)⇒ **同一机制两套互斥规格**;④ **代码复评 #2 #6**——**第五处副本**藏在 §6.3 的**代码块**里(前四次扫描都只看表格)。⇒ **专门治它的批次,被它抓了四次。** - **登记面新增**:**R-33**(「登记说错了」同物种)· **R-34**(**`self-consistency ≠ protection`**——两侧同函数同基只证明「一致地锚在同一个根」,**不证明那个根是对的**)· **R-35**(**全局钩子拿不到会话身份**——`makeDocFreezeGate` 注册为全局 `tools/pre-execute`,闭包只吃 `getFreezeSet` ⇒ **任何需要「按会话」的判据在此形态下无法实现**;这是 O-E5 修法方向的**结构性前置条件**)· **R-36**(判据注入无预算 · R-25 反向腿)· **R-38**(`engCoderEffort` 默认 `low` 在不支持 `low` 的模型上**必然静默降级到 `off` = 推理全关**,而 README 让人照抄的正是 `low`)· **R-39**(V1/V7 无常设锁)· **D-38 → 已修**。**R-37 已撤回**(父侧误登「设置页没有 `budgetCapMs` 输入框」——实测该输入框在 `client.js` 里,label 还写着「须低于 `run_code` maxWallMs」)。 - **本批不可真机验证**:进程 cwd 与会话 cwd 是否真会分叉(需活会话观测;这也是 O-E5 不修的站得住理由之一)· 设置页第三个输入框的交互 · 补第 7 维后评审员的实际行为。**本批触碰 `lib/**` ⇒ 需重启 DSH 才生效。** - **测试**:`node --test` **453/453**(**基线 453 + 本批 0 新用例**——四类断言全部并入既有块)· 变异合计 **9 条 + 谓词级三形态探针**(逐条附「打红的是哪条断言」、**全部 byte-exact 回退 + sha256 复核**、**无假红**)。 ## [0.20.0] — 2026-09-15 > **一句话**:把一处「逐字节保存整个函数体」的测试锁收窄为**语义锁**(以后改一行注释不用再重刷 79 行)。 **批 12:`WRITE_GATE_FIXTURE` 锁面收窄 —— 把「改一行注释就要重刷 79 行夹具」变成「只锁可执行行」,并在过程中由独立审计揪出一个本批亲手引入的假绿面** - **锁的形态**:`WRITE_GATE_FIXTURE` 原本逐字节保存 `lib/eng.mjs` 里 `makeWriteGate` 的**整个函数体**(**79 行 / 4821 字符**,其中 **32 行是注释 = 40.5%**)⇒ **改一个字的注释就要把那 79 行整个重刷一遍**(批 11 **被迫三次**)。现在只锁**可执行行**:`stripComments`(**实现零字节未动**)→ 逐行 `rtrim` → **丢空行** ⇒ **47 行 / 2677 字符**,字节与顺序仍逐字节锁死。 - **「丢空行」不是可选项,是实证结论**:剥离会留下**注释骨架**(32 个仅缩进的伪空行 + 1 个尾空格)⇒ **只去注释的话,删/改/挪/增四种纯注释改动全部仍会红**。四家会诊在这个点上给了**相反**答案,父侧逐条实测裁决:**glm 对**;反对丢空行的两家,其理由(「会削弱注释/代码位置互换的检测」)**在事实层面不成立**——位置互换是**代码行顺序**变,丢空行不影响。 - **★ 一条真漏洞,由独立审计抓到,且是本批引入的**:`stripComments` 的行注释规则 `(^|[^:])\/\/[^\n]*` **只认 LF**,而 JS 的 **LineTerminator 是 LF \| CR \| U+2028 \| U+2029** ⇒ 在 `//` 后插入 CR/LS/PS 时,**解析器认为注释在其处结束(后面的代码是真的)、剥离器却把整行吃掉** ⇒ **代码在锁眼里消失、写门禁实际变成 fail-open,而 A2 全组保持绿灯**。收窄前的逐字节锁对任何字节改动**必然红** ⇒ **这个缝是收窄带来的**。修法 = **先统一四个行终止符再剥离**;父侧**先验后改**(三种终止符 假绿→红 · 整片 CRLF 不误伤 · **归一态逐字节不变 ⇒ 夹具无需重推**)。 - **而设计档里恰好写着一句为它背书的话**:§9 边界-3 原文「**该弱点在 A 下只可能误红、不可能假绿**」——**被我自己的推理写下的、并被一个具体变异证伪**。我的推理**只覆盖了「字符串里含 `//`」这一形态**,**完全没想到行终止符这一维**。⇒ **元教训(已落设计档 §13.2.0)**:「**对『某个弱点不可能造成 X』的断言,本身就是最需要被对抗性测试的一句。**」 - **三个一行守卫堵三个真缺口**:① **顺序锁**——`wgEnd` 的定位标记**本身就是注释**(`indexOf` 原始 **15722** / 剥离后 **−1**)⇒ **必须先切片、后剥离**,而该顺序**此前无任何断言守着**;② **域自检**——剥离器自述「对字符串含 `//` 的场合足够」,**升格为等值参与者后该声明承重相等正确性** ⇒ 从注释升级为断言;③ **合并签名正则**——`"frozen".includes("designFreezeSet")` 是 **false**(freeze **无 n**)⇒ **旧断言从没拦过「把预闸函数拖进写门禁」这类合并**(D-E1 要防的最重一类),它只拦 `frozen: ` 文案。**这三条都是「今天就已经存在」的缺口,不是本批新增的。** - **两条被证伪后修掉的判据**:① **AC-6** 原写「先剥后切 ⇒ 该断言红」——**实测不可能**(该变异下 `wgEnd = −1`,**先撞边界断言**,顺序锁根本到不了);改为「在位 + 在明确输入下是唯一失败断言 + 如实登记其价值是把不变量写成可读断言」。② **AC-9 的幂等自检是 present-but-inert**——`norm` 恒在 `normalizeExec` 的像集里 ⇒ 等值断言必然先红,**7 种夹具污染下它的消息从未出现**;改成**三条直接判定夹具原始文本**、**全部排在等值断言之前**(经字节偏移复核)。 - **接受的代价(明写,不靠猜)**:**注释层完全自由**(文字 / 位置 / 行数 / 缩进)⇒ **注释与代码脱节从此无锚捕获**,权威 = 设计档 + 评审;**两个例外**(切片内新增 `/*` ⇒ 域自检红;改右界标记文字 ⇒ 边界断言红);**切片内行尾风格不再致红**(行终止符统一的必然结果,已登记)。 - **零改面**:`lib/**` **零字节**(**含 `lib/eng.mjs`**)· 两个 `.txt` 夹具零触碰(受 T-AP9 锚 B + 口径锁双重保护,**结构上改不得**)· **基线那十档**逐个零编辑 · `stripComments` 两条正则逐字节不变(它同时承 T-E18 计数与 A8 负向断言)· 顶层 `test(` 恒 **24** ⇒ 台账零联动 · 无新增测试档 · 历史记录零改动。 - **新增缺陷登记 D-38**:`lib/eng.mjs:471-472` 的活注释把锁归给了一把**不存在**的锁(称 `stage-gate` 逐字节锁该切片,而它只查四个禁用标识符)——**与 `:52` 的「措辞过宽」性质不同**(那是锁存在、覆盖面写大;这是指向不存在的锁)。两处受本批 **N-1 冻结**不可修 ⇒ 登记 + **触发条件 = 下一次触碰 `lib/eng.mjs`**。 - **流程留痕(本批一半价值在此)**:会诊 id 1 **4/4 交付**(**§1.1 是裁决分歧的实证表**)→ 设计评审**轮次 1 即 `PASS`**(🔴0,对比批 11 烧了五轮)→ 实施 17 变异 → **独立分歧审计 🔴1 🟡3 🔵4** → **设计修订** → 修复轮 **25 变异** → 交付代码复评 **`VERDICT: PASS`**(两轮共 13 条发现,全部处置)。 - **三条本会话新长的纪律**:① **收窄一个锁,就是在给攻击面搬家**——旧锁的「对任何字节改动必然红」本身是一种强度,收窄必须重新论证**逃逸面**,而**论证要实测、不能推理**(我就推理错了)。② **「修正一处错指针」这个动作本身会引入新错**——我在改正 §13.2.2 时把 `WRITE_GATE_FIXTURE` 的区间写成了它并不覆盖的那一段。③ **一个会诊结论可以有对的结论 + 错的机理**(v4-pro 的「预剥离夹具」结论对、逃逸机理错;kimi 的「零反引号」事实错)⇒ **会诊/审计也要逐条回盘**。 - **测试**:`node --test` **453/453**(基线 453 + 本批 0 新用例——本批**不新增测试用例**,全部断言并入既有 `test(` 块 ⇒ 顶层计数恒 24)· `ledger-parity` 6/6 · 发布门 **6/6** 机械闸 · 变异合计 **42 次**(17 + 25),**逐条附「打红的是哪条断言」、每次逐字节还原 + SHA-256**。 ## [0.19.0] — 2026-09-13 > **一句话**:文档纪律成文:D1–D7 从「被引用 22 次的未定义编号」变成**可读的七条法律**,落进 `METHODOLOGY.md`。 **批 11:D1–D7 文档纪律成文 + 上游失败路径语义 + DOC-HYGIENE 提示词级纪律 —— 把三条只在上游或只在我们脑子里的纪律落到本仓,且形态自含可判** - **D1–D7 从「引用 22 次的未定义编号」变成可读的七条法律**。这七个编号在本仓被引用 **22 处**、**零内容**——真正的内容只活在上游 `src/prompts/discipline-engineering.md:103-111`。现在它住进 `METHODOLOGY.md` 的新子节,**规范层零状态句**(规范里没有会过期的句子 ⇒ 不会漂),「我们做到哪了」是一份**带 as-of 提交的归档快照**、只活在设计档 §10(快照**只会老、不会漂**)。上游关系取**改写 + 出处行**:既不照抄(会上游含本仓**已废除**的名词与**不存在**的载体 ⇒ 进口未定义名词),也不纯引用(上游 checkout **不随插件发布** ⇒ 规则对发布产物不可见)。逐条现状:**D1 写权矩阵完全缺席**(本批新写)· D2 已有(**收编**既有的两条 bullet,否则新子节第二天就违反 D2 自己)· D3 部分机检 · D4 部分 · **D5 已实现为机制、未写为纪律**(批 6b 守卫 E,本批成文并指向该机械腿)· D6 部分 · D7 一半。 - **D7 的处理值得单独说**:上游 D7 含「变更留痕 + 核销同步」两半,而**「核销同步」已在批 10 明文废除**(R-20)。本批的写法是 **指名道姓地否定它一次**——「上游 D7 另含『核销同步』半条——**不吸收**(R-20 已废除该名词)」——**废弃一个名词的正确方式是否定它一次,而不是假装它没存在过**。 - **失败路径不再是死路**。上游定义 11 条失败路径,我们只有 ~17 个非成功返回、各自带一句 detail 与 ≤2000 字符的 partial——**没有「不自动重派」的明示、没有可选项菜单、没有用户裁决框架**。现在一个固定后缀块由 `failStop()` 追加到**每个终态失败返回点**(**17 个,父侧逐点核对全部在 spawn 之后**),给出**五项恢复菜单** + 不自动重派 + 三要素(崩在哪一步 / 已改了什么 / 哪部分未动)。**菜单是五项而非上游四项**:「**改道**(换执行通道或策略)」是对上游清单的扩展,**必要性有实测**——批 10 的 codex **连续两次 `PROCESS_ERROR`**,上游四项里**没有一项对症**(重派会用同一坏通道再死一次)。同时把 `lib/index.mjs:937` 那条**现行的自动重派指令**同批划界:保留「子代理**自报**阶段失败 ⇒ 重派一次」(出口干净、工作区可检),新增「崩溃/超时/abort ⇒ **不自动重派**」(出口脏、工作区未知)——**不划界就会留下两条互相矛盾的纪律**。 - **两类失败靠返回点位置区分,不靠内容判据**:预检拒绝(task 缺失 / token 门 / in-flight 单飞——**工作未发生**,自带补救指引)发生在 spawn **之前**,终态失败全在 spawn **之后** ⇒ wrapper 只挂后一组。**明令禁用内容判据**(那会与本批「谓词要写全」的标准不符)。 - **DOC-HYGIENE 有了在所有会话都看得见的提醒面**:落 `lib/prompts/discipline.md`(经 `systemPrompt.section` 注册,**进每个会话含主代理**),而不是只在工程模式注入的 `engineering.md`——因为文档卫生的病(计数漂、悬指针、静默写坏)**所有模式都咬**。五条各带可判谓词:计数与列表同改 / 指针可解析(**档名:§节**,禁「见上」)/ 写后回读 / 状态行与事实同步 / 描述面与实现一致。 - **「写入静默失败」终于有机械网**:新增 `test/doc-hygiene.test.mjs`(4 用例)做两件事——**U+FFFD 全仓扫描**(扫描面 `>= 80` 档 + **probe 文件必含**自证,防「空集合假绿」;落地前实测全仓 = 0 ⇒ **零白名单**)与**五常设档 canary**(各含真实顶层标题/表头锚 ⇒ **截断为 1 行立刻红**,本会话真发生过)。本仓三次实证的静默写坏(PowerShell 双编码 · shell 单行脚本把 METHODOLOGY 截成 1 行 · U+FFFD)由此进网。 - **本批没花掉那笔用不上的授权**。用户预接受了「T9 夹具重新基线」,而**四家会诊一致反驳其前提**:T9 测的是 `buildCoderBrief` 在 **stages 缺省**时的输出,persona 与工程模式主提示**都不在该路径上** ⇒ **`test/fixtures/**` 与 `lib/prompts.mjs` 本批零字节**。两家更进一步指出:今天连改也改不了——`AP_TEST_AUTHORIZED` 的口径锁要求 `.test.mjs`,而夹具是 `.txt`。 - **流程留痕(本批一半价值在此)**:会诊 id 2 **4/4 交付**(codex 在连续两次 `PROCESS_ERROR` 后**首次回话**)→ **设计评审五轮**(FAIL/FAIL/FAIL/FAIL/PASS,四轮的 🔴 **全是同一类**)→ 实施 9 stages → **独立分歧审计** → **交付代码评审**。**★ 本批的元教训是本会话最重的一条**:「**规范/判据自己打自己**」累计命中**六次**——批 8(设计档改写自己纪要的裁定)· 批 10(文档卫生交付物自己引入错数字)· **批 11 五次**(D1 vs 实施域 · 规范块 vs 自身 V2 · V4 锚 vs 冻结文本 · V11② 锚被加粗星号切断 · **交付期 V5 把 `METHODOLOGY.md` 列进六串负向清单而该档全文含 3 处 `/methodology/i`**)。**同一根因贯穿全部六次:靠「看起来对」代替「机械核一遍」。** 对策已落:stage 0 第 ⑥ 项「**锚串存在性自检**」+ **设计交付评审前先跑该自检** + **判据的覆盖面与谓词必须一起自检**(「清单里每个档,是否可能通过该谓词」)。 - **测试**:`node --test` **453/453**(基线 449 + 本批 4 新用例)· **零改面八项**:`test/fixtures/**` ×2 · `lib/prompts.mjs` · `stages`/`stage-gate`/`preset-static`/`test-lifecycle` 四档 · **基线 10 档全未动** · `lib/**` 六串精确谓词命中 **0** · 三处「逐字交付」文本(§6.1/§6.3/§6.3b)经**字符级验证**确认逐字落地 · 台账 **20 档逐档用例数相符、合计 453 == 实测**。 ## [0.18.0] — 2026-09-13 **批 10:台账纪律(可落地版)+ D-31 + D-36 + 文档卫生 —— 让计数由机器算、让登记项指得出落点、堵死一条静默清空配置的数据丢失路径、消掉重启后写门禁的纯摩擦** - **D-31(本批最严重的一条,实测为数据丢失而非排版问题)**:未知**顶层**配置键原先走 `notes` 而非 `errors` ⇒ `ok` 仍为 `true` ⇒ PUT 返回 **200「已保存」** ⇒ 而 `saveUserConfig` 是**整体替换** ⇒ **整层 user 配置被静默清空**。实测证据:旧行为下一次全未知键 PUT 使磁盘从 `{"config":{"advisor":…,"engCoderMaxTokens":65536}}` 变成 `{"config":{}}`。**修法取「报错」不取「回显」**:键移入 `errors` ⇒ **400 且不写盘**(在 `saveUserConfig` **之前**拦下)⇒ **该路径灭绝**。**决定性理由**:**嵌套**未知键今天**本来就是报错**(三处测试在案:`context-budget`/`config-api`/`path-kind`)⇒ 顶层是全函数**唯一的例外分支**,本修复**不是引入新严格性,而是恢复函数自身的一致性**。回显方案被否决——它只把「静默丢」变「响亮丢」,字段照样不落盘。**整体替换语义不改**(full-state PUT 下「删除键」正是靠**缺席**表达;改 merge 会让设置页**无法清空**任何字段)。另加一条**客户端 ⊆ 服务端白名单**一致性锁(两侧都从源码字面解析、不手写清单)消除「设置页造出服务端不认的键」的死锁顾虑。 - **D-36(重启后写门禁的纯摩擦)**:`designToken` 是会话内存态,重启后写门禁拒绝主代理对已批准设计的**小修**。**修法 = 门禁在内存态为空时显式读盘**,判据与内存路径**完全同构**(同一个 `validateDesignToken`),过期/无记录/他会话一律**拒**,放行时**落一行日志**并回填。**失败方向全朝拒绝**:读盘失败**不**落入门禁自身故障的 fail-open catch(**层级区分**:读盘失败 ≠ 门禁故障)。**`docHash` 不进判据**——内存路径今天也不查,盘路径查会造成**两路径判据不一**(那才是真的不一致);「文档被改后令牌是否失效」是**另一个命题**,若做须两路径同做,**另立批次**。**★ 这是对既有裁定 D1 的明示翻案**:D1 当年护的是「盘 token 经一次调用**静默**驻留 state、**间接**打开主门禁」;门禁显式读盘**终态相同、路径不同**,且**留痕** ⇒ 可审计性高于现状。能力层面「回填路径**早已存在**」(主代理本可读盘、带原值调 `eng_coder` 触发回填)⇒ **内存态从来不是能力边界,只是摩擦**。设计档设 **§10「推翻 D1」专节**,`eng.mjs` 的旧注释**同步改写**(注明翻案理由并指向该节)——**否则下一轮分歧审计会把本修复当回归抓出来**。 - **台账纪律(可落地版;「老化」与「机检三件套」明确不做)**:新增 **`test/ledger-parity.test.mjs`**(6 用例)机检「**文档自报的计数 == 机械数出的行数**」——**期望值住在文档里、实算值从文档字节读,测试源零计数数字字面量**(否则就是又一个手写期望值)。覆盖缺陷登记表与吸收清单 §2;**交接页刻意排除**(它是 `release-check` G7 的**人眼闸**,评审 #3 的正式收窄,不翻案)。**空面按红**(实算 0 / 自报句解析不到 ⇒ 红,不许 `0 == 0` 沉默通过)。**落点只放测试档、不进 `lib/**`、不加发布闸**——因为 **G2 的档清单是 `readdirSync` 派生的**,新档**自动进发布门**,而再复刻一条闸就是「同一谓词两处拷贝」(违判据单一权威)。**首跑即红**:登记表自称 33 条而实测 **36 行**、吸收清单自称 43 档而实测 **42 行**。另给缺陷登记表加**指针**(只机检「文件存在」——**行号不机检**:行号是位置提示不是身份,锁它会制造必然噪声,**D-37 的教训**)与**触发**(闭合四值 + 状态↔触发交叉不变式)两列,36 行逐行填充;**吸收清单不加指针列**(其指针指向**上游**批次档、**本地不可验** ⇒ 不可验的指针列比没有更糟)。 - **吸收清单三处矛盾订正(以 §6 为排期权威)**:依据是 `:6`「批次排序、优先级、以及哪些明确不做都以本档为准」与 `:160`「排序变更以本档为准…并在 §6 记一行变更理由」。§2 的 POOL-LEDGER 行自「批 11」改挂「批 10」并收窄为可落地版;§6 的批 10 行按实际四件重写;**§6 补批 11 行**(D1–D7 文档纪律,含对 §2 建议批次列的**全量对账**)与**变更理由行**(补 §7 要求的手续);**§5 新增「老化」不吸收条,§5:126 一字不改**(**改的是写错的那一处,不是立场**);「核销清单」一词废除(全仓唯一出处即该格、从未定义)。 - **文档卫生**:**十二处**滞后状态行订正(9 个文件的第 7 行 + 1 处括注形态 + 1 处地图内嵌 + 1 处 `PROPOSED`),另补一处**逃过字面 grep** 的(`codex-runner-research.md` 写「设计定稿待评审,未实现」而 runner 早已实施)并把它登记进文档地图。 - **流程留痕(本批一半价值在此)**:会诊 id 1 **3/4 交付**(codex **第二次** `PROCESS_ERROR`,如实登记不补位)→ 设计评审轮次 1 **`PASS`**(🔴0 · 🟡6 · 🔵3;评审员的第一句评语是「**好几条意见恰恰落在本批要消灭的那类缺陷里(自报计数与枚举漂移)**」)→ 实施 8 stages(23 次变异)→ **独立分歧审计 🔴0 · 🟡3 · 🔵7**(审计员在**临时副本**上**独立复现**了全部 M 锚的红,确认 **27 条 AC 与 15 个锚零恒真锁**)→ 两轮修复轮 → 交付代码评审**轮次 1 `PASS`**(🔴0 · 🟡0 · 🔵5;首次 900s 超时,上调至 2400s 后通过)→ 收尾修复轮。 - **本批沉淀的纪律**:① **自报计数漂移在本会话累计 11 次命中,其中 6 次发生在批 10 自己身上**——而这一批的立论正是「自报计数会漂、要机检它」。它的机械闸**只罩两个面**(缺陷登记表与吸收清单 §2),**其余十个面仍是人工**;这条**「哪些面已机检、哪些仍是人工」已明确写进交接页**,防下个会话误以为全覆盖。② **锁面含注释的元锁有真实维护代价**:`WRITE_GATE_FIXTURE` 逐字节锁住 `makeWriteGate` 函数体**含注释** ⇒ 任何注释改动都要重新基线(本批**三次**:代码→注释→代码,**每次都自白**;第一次未自白正是审计 #3 的病灶)。③ **实现者的 self-report 也要回盘核**:收尾轮自报「legacy 令牌在两条路径措辞可能不一致」,父侧逐行走代码后判**不成立**(`gateExp` 对内存态取 `tokenExpiryMs(该令牌)`,legacy 的第二段是**可解析的过去时间戳**)⇒ 两路径对 legacy **一致**。 - **测试**:`node --test` **449/449**(基线 434 + 本批 15)· 基线 10 档中**只有** `test/config-api.test.mjs` 被改(走 `AP_TEST_AUTHORIZED` 授权 + 反证实验:摘除 ⇒ T-AP9 红 ⇒ 还原)· **零改面**:`release-check.mjs` / `lib/client.js` / `lib/config-store.mjs` / `package.json` 全零 diff · `lib/**` 内 `check-doc-width`/`check-ledger` 命中 = 0 · 台账 19 档逐档用例数与实测**逐档相符**(449 == 449)。 ## [0.17.0] — 2026-09-13 **批 9:测试生命周期三层 + 发布门 + verify 宿主侧门禁 —— 给「只进不出」的套件立规矩,给 git tag 发布装一道会真的被跑的体检** - **痛点不是「测试太多」,而是「锁坏了没人知道」+「数字漂了没人知道」**。实测证据:D-37 两次返工(锚的谓词写错);批 7 审计抓到 `AC-P13` 静态锁**自称覆盖六子点、实测只证 4 个且一条断言是 tautological**(审计员定性原话「代码全部合规,是锁缺陷」);**342 vs 340**——同一事实在交接页与 CHANGELOG 里两个值。而套件从项目开始**一条都没删过**(`git log --diff-filter=D -- test/` 为空),用例数单调增长 274→434。 - **① 测试生命周期台账**(`docs/test-lifecycle.md`):三层**按处置政策定义**(非按测试风格)——① 批次脚手架锁(锁易变施工事实)② 插件行为契约(锁对外可感行为)③ 实况事故收编(由真实死亡/复现事件补入,`death-provenance` 整档即此形态)。附**退役判据 R1–R4**(死契约 / 衰变锁·tautological / 被取代的重复 / 环境代理断言,全部须举证;明确排除「大小 / 年龄 / 久未失败」)+ **R0 排除项**:**元锁档(`death-provenance` / `guard-e`)在其锁存活期间不可退役**——删它们等于**删除锁本身**,套件照样全绿而锁已消失,这是唯一能「安静地」拆掉整个锁体系的路径。**本批退役 0 档、不设配额**(预设配额正是假退役的制造机;上游「9 退 4 并」是他们的扫描结果不是目标),**taxonomy 住台账不住文件系统**(⇒ 零触碰 T-E19 / T-AP9)。 - **② 发布门**(`release-check.mjs`,仓根单文件、零依赖、零 DSH 态):七闸——G0 语法 · G1 工作树卫生 + tag 不存在 · G2 全量套件(`--test-reporter=tap`,**TAP 是稳定机器格式**;零重试)· **G3 计数一致**(运行数 == CHANGELOG 顶部计数,**这正是 342/340 那族当时就能被抓住的地方**)· G4 版本三处一致(package.json == CHANGELOG 顶 == 拟用 tag,`vX.Y.Z` 去前缀比较)· G6 flake 预案(失败项**全部**命中已知 flake 表 ⇒ 允许复跑一次;两跑结果**由人**登记,**脚本不写任何文档**)· G7 人工核验项(明说是人眼闸、不进退出码;G5 号段保留)。**形态裁定**:否决纯 `gate-as-test`——**计数相等性自指**(`node --test` 的用例数包含门自己这个测试档,而 CHANGELOG 的 `N` 要等全绿之后才写,**单次运行内不可能成立**);但采其一半:**不依赖计数的门断言做成常驻套件内用例**(写进脚本的东西可能永远没人跑)。 - **③ 阶段门**(`lib/eng.mjs` 的 `stageGateNote`):任务书**早已强制**「报告必须以阶段状态表开头」,但宿主**从不读它**(只判 `stopReason`)⇒ 「completed 但自报 failed」照样簿记。本批补一个**纯解析**的门:表缺失 / 任一 `failed` / `skipped` 无理由 ⇒ 在交付文本**前部**加响亮横幅,**4 个成功返回点**全接(与簿记点**结构配对**),失败/abort 路径不接。**后果取「响亮横幅 · 不阻断」而非阻断簿记**:与上游先例一致(`completion.mjs:70-74`/`:118-122` 明言工程模式**刻意**用 flow-driven review 而**非** per-turn mechanical pushback),且 `mutatedThisRun`/`touched` 描述的是**磁盘上真实发生了什么**——跳过簿记会让评审链看不见真实变更,**制造更糟的静默**。 - **明确不收**:宿主复跑 stage 的 `check` 命令。六条实测代价——① `check` 是**自由文本**(要可执行就得约束形态 ⇒ 撞 T9 字节 fixture;不约束则**把模型输出当 shell 跑**的注入面)② 阶段纪律本就要求子代理先跑过 check(必然重复)③ dsh 同步路径本就在 budgetCap 墙钟内赛跑(会**把好交付变成超时 abort**)④ 边际收益≈0(复跑多抓的只有「撒谎的子代理」,而交付评审本就强制跑测试)⑤ R-13 这类 flake 会让本就冤杀 ⑥ **逆上游架构裁定**。**登记为观察位**(出现「申报与实测不符」的实测案例即重新评估)。 - **流程留痕(本批一半价值在此)**:会诊 id 1 **3/4 交付**(codex `PROCESS_ERROR` 如实登记不补位)→ 设计评审**轮次 1 `FAIL`**(🔴2:① 台账覆盖面自相矛盾——三处写死 14 而本批新增 3 档 ⇒ 交付时 17,照文实施必红;② **我静默改写了自己纪要里的裁定**——纪要把版本一致性列入常驻断言、设计档写不做且无留痕,评审员还**反驳了我的时序理由**并判其成立)→ 修 12 条 → **轮次 2 `PASS`** → 实施(8 stages,23 次变异)→ **独立分歧审计 🔴1 🔵5**(🔴 = **一条永不失败的断言**:R0 门锁用**裸档名**比 `AP_TEST_AUTHORIZED` 的**仓库相对路径** ⇒ 恒真;而**同档注释自己就写着「按裸档名比对必然全 miss」**——**一个专门用来消灭假锁的档案里出现了假锁**)→ 修复轮 → **交付代码评审轮次 1 `FAIL`**(🔴 = **G6 复跑预案结构性死代码**:复跑**只在第一跑红时触发**,而 G2/G3 已按第一跑固化、复跑成功**只打印不回写** ⇒ 「放行」永远不可达,**US-12 的核心承诺是实现不了的**)→ 修复轮 → **轮次 2 `PASS`**(🔴0)→ 收尾修复轮(四条 🔵)。 - **本批沉淀三条纪律**:① **设计档交付前,把本批纪要的裁定清单逐条 grep 进正文做交叉自检**——不靠记忆(本批 🔴#2 与批 7/8 共 6 次同族命中,且这次是**我写下该纪律之后立刻又违反**);② **新写的锁必须立刻做变异自证,而且变异值要取自「实现/配置的真实形态」,不是取自断言自己的写法**(🔴#1 正是实现者构造变异时下意识用了与断言一致的口径而漏掉);③ **台账 §三的用例数与 fs 实测是等值锁 ⇒ 任何新增用例都必须同时改台账**(实现者实测撞到并正确地没越界,改把断言并入既有用例)。 - **登记面**:R-13 `session-state` T6 时序 flake(**本批不修**,基线档 + 不在写域;发布门 G6 预案先写死)· R-14 宿主复跑的观察位 · R-15 codex 会诊失败 · R-16 会诊把上游路径写错(**内容为真、路径记错**)· R-17 交接页曾有重复批 9 行 · R-18 台账判据的**语义**正确性只能靠评审人眼,机械只保形式(诚实边界,不超卖)。 - **测试**:`node --test` **434/434**(基线 413 + 本批 21)· 基线 10 档零 diff(T-AP9 绿)· T9 fixture 字节不变 · `lib/**` 子进程调用点仍为 1(零新执行面)· 零新依赖 · 台账 17 档双射(首扫 14 + 本批 3)· 新增测试档三份均按 T-E19 登记制登记(含理由与用户裁定引用)。 ### 补记 · 移植远端分叉 `7b6a845`(v0.9.3)的增量 **移植远端分叉 `7b6a845`(v0.9.3)的唯一真增量:把会诊看门狗预算的解析提到单一事实源** - **背景(避免重造)**:`7b6a845` 与本仓批 1 的 D-29(`9ae1277`)是**同一件修复的两次独立实现**——都让 `consultTimeoutMs` 在 user 层可配。本仓 D-29 已把两键补进三面白名单(PUT 校验 ⊕ merge 白名单 ⊕ 运行时读取)并含设置页接线与全链路测试,**功能是完整的**。故本批**只取它独有的那个精炼点**:把看门狗预算的解析从 `lib/consult.mjs` 的就地三元式提到 `lib/config-store.mjs` 的**单一事实源**(`resolveConsultTimeoutMs`,带非法值告警与跨升级保留语义),而不是散在消费点。 - **值域裁定(重要)**:**下限取 `30_000`,不取远端的 `60_000`**。理由:批 1(D-29)已发布并测试了 `CONSULT_TIMEOUT_MIN_MS = 30_000`,把下限抬到 60000 会让**已存在的合法用户配置**(30000..59999)静默变成非法而回落缺省——那是行为回归。远端那个 60000 是它独立选的,没有任何依据要求我们跟随。⇒ `CONSULT_TIMEOUT_MIN_MS` / `CONSULT_TIMEOUT_MAX_MS` **继续复用批 1 已发布的两常量(未重复声明)**,新增的只有 `CONSULT_TIMEOUT_DEFAULT_MS = 600_000`。 - **不是照搬,是移植结构**:远端的 `config-store.mjs` / `consult.mjs` 是本仓 **0.9.3 时代的旧版本**,照搬会**回退其后 17 个提交的改动**。本批只取「resolver 这个函数 + consult 改调它」两处**结构**,内容按当前 HEAD 写;`consult.mjs` 的 D-28 `abort-provenance` 导入**保留**(两条 import 并存,静态断言锁住不得互相覆盖)。 - **落点**:`lib/config-store.mjs`(`CONSULT_TIMEOUT_DEFAULT_MS` + `resolveConsultTimeoutMs`,置于 `resolveDshBackgroundTimeoutMs` 旁保持命名习惯)· `lib/consult.mjs`(删本地 `const CONSULT_TIMEOUT_MS = 600_000` 与就地三元式,改 `resolveConsultTimeoutMs(config)`)· `test/codex-runner.test.mjs`(验收断言)。 - **测试**:`node --test` **434/434**(基线 434 + 本批 0 用例——四条验收断言**并入** `test/codex-runner.test.mjs` 的三条既有用例,顶层 `test(` 计数不变 177,形态理由见下)· **变异矩阵 10/10 逐条自证会红**(resolver 恒回落 / 非法值不告警 / `DEFAULT` 改值 / `MIN` 改 60000 / `MAX` 改值 / 消费点回落就地三元式 / 恢复就地常量字面 / 删 config-store 导入 / 删 abort-provenance 导入 / 删授权登记 ⇒ 各命中目标用例红,还原后绿、还原逐字节核验)· 零新依赖 · 基线 10 档零改动(**唯一例外 = `test/codex-runner.test.mjs`——即本批的授权档本身,已在 T-AP9 的 `AP_TEST_AUTHORIZED` 逐档登记**)。 - **形态说明(诚实登记 · 两条)**: 1. **断言并入既有用例而非新建顶层 `test(`**——因为 `test/test-lifecycle.test.mjs` 的 **T-LC2** 是「台账 §三 用例数 ↔ fs 实测 `test(` 计数」的**等值锁**,新增顶层用例会立刻让它红,而 `docs/test-lifecycle.md` **不在本批写域**。这与批 9 交付代码评审修复轮的先例同一形态(父侧当时裁定接受)。若父侧希望四条断言各占一条独立顶层用例(全量 **438/438**),需**同一交付内**把台账 §三 `codex-runner.test.mjs` 行由 **177 改为 181**——该授权不在本批写域。 2. **写域外的一行(需父侧裁可)**:`test/death-provenance.test.mjs` 的 `AP_TEST_AUTHORIZED` 由 `["test/design-review-guard.test.mjs"]` 扩为含 `"test/codex-runner.test.mjs"`(+ 注释说明来历)。**动因(实测证据)**:任务书声明 `test/codex-runner.test.mjs` 是授权档,但 **T-AP9 锚 B** 的真值是「基线 `2e6ca8b` 时点**已存在**的测试档零修改,除 `AP_TEST_AUTHORIZED` 例外」——而实测 `git diff --stat 2e6ca8b HEAD -- test/codex-runner.test.mjs` **为空**(该档自基线起零改动、确属基线档),故任何断言并入都会让锚 B 必红(本批实测:全量 **433/434**,唯一失败即 T-AP9)。该清单是仓内文档确立的**唯一合法通道**(`docs/test-lifecycle.md` §一;`docs/2026-09-13-test-lifecycle-consult-minutes.md:26`「授权通道存在……基线**后**档 ⇒ 可改」),且 `death-provenance.test.mjs` 本身由批 6 `a5f9551` **建于基线之后**(`git ls-tree 2e6ca8b -- test` 不含它)⇒ 扩清单**不触发**锚 B。**未削弱锚的语义**:仍是「逐档显式例外」,任何**其他**基线档改动照旧红;变异自证 **M10**(删去该登记)⇒ T-AP9 红、失败文本点名该档、还原逐字节一致。 - **版本号**:本小节是 `[0.17.0]` 的**同版本补记**,**2026-09-17 已并入本版本小节**(它当年单独成节、且置于 CHANGELOG 顶部,是为了——机器契约 `G-常驻2` 要求「CHANGELOG 顶部版本头 == `package.json#version`」,改写成「未发布」会让常驻断言立刻红);最终版本号由父侧在收口时定。 ## [0.16.0] — 2026-09-13 **批 8:提示词公共层(裁减版)—— 判定契约从「碰运气不漂移」变成「由 spec + 双向锁钉死」** - **形态 = 零改面**。四个 advisor 提示词(`advisor-round1/2/3.md` + `advisor-design.md`)与 `lib/prompts.mjs` **零字节改动**——本批**不搬运任何提示词字节**。交付物 = 一份契约 spec + 一条双向锁测试。 - **为什么不用真抽取**:可逐字节抽取的只有 **4 行**(标题 / 引导句 / `Tolerated:` / `malformed-refused`),去重收益 = **12 行**;而代价是 ① 新增**启动期**失败面(`prompts.mjs` 顶层急读,片段缺失 ⇒ 整个插件加载失败)② **必须改写 `AC-V13`/`AC-V13b`**——本仓**最安全相关**的两个测试(它们**直接读裸档字节**,抽取后裸档不再含那 4 行,必红)③ 破坏「零构建」这一既存性质。**为 12 行去动最硬的锁,风险收益倒挂。** - **交付物**: - `lib/prompts/verdict-contract.md`(**新增**):判定族的公共层权威——① 契约字面(四档逐字共享的 4 行)② 差异表(**ID 化 `` 哨兵**,使「恰 4 类带意差异」可机器判定)③ **四档 Verdict 块逐字快照**(程序抽取写入)④ **出现点地图**(四档 + `advisor-msgs.mjs` 的 2+2 处 + `AC-V13`/`AC-V13b` 锁位置 + `VERDICT_LINE_RE`)⑤ 已存在的漂移登记。**该档不进装载路径**(`loadPrompt` 按显式文件名装载,库内无 `prompts` 目录扫描)。 - `test/prompt-contract.test.mjs`(**新增**,14 用例):**两组件闸** + 哨兵集合 + 四条不变式 + DIFF 哨兵 + 地图内容 + EOL 契约 + 装载路径零接入。 - **闸的两组件(这是本批的要害)**:**组件①** = spec ↔ 四档**逐字一致**;**组件②** = **四档 Verdict 块的 sha256 内容指纹**(硬编码常量)。① 是**共拥有**的锁——「提示词 + spec 快照」**协同改**能全绿;② 把块的归一后内容钉成常量,**协同改即红**。**设计评审轮次 1 的 🔴 正是指出纪要写了两个组件而我设计档只落了一个**;修复后**审计员独立复现 M3**:协同改 ⇒ 组件① 绿、组件② 红。 - **反向锁**:三版 PASS 判据句 / 两版 FAIL 判据句 / `Never translate` 的 3:1 措辞差 / `design` 的独有两条 —— **一律原地保留、只登记不合并**。统一任一措辞 = **替换** ⇒ 违反「判定族字面只追加、绝不替换」。另加**负向锁**:三版 PASS 判据句**必须互不相同**(防后人「顺手统一」)。 - **流程留痕**:会诊 id 3(**4/4 交付**)→ 父侧实测(可抽取集 = 4 行 · 四档块 11/10/10/17 行 · 本机 603/603 CRLF)→ 用户裁定形态 = **零改面**(否决组装层)→ 设计评审**轮次 1 `FAIL`**(🔴1 🟡7 🔵5;🔴 = 「裁定写进纪要 ≠ 设计档兑现」**第五次命中**)→ 修 13 条 → **轮次 2 `PASS`**(残留 7 条含**尾换行不对称会让 AC 恒红**的真缺陷,父侧全部修掉)→ 实施(后台一次通过,18 次变异自证)→ **独立分歧审计 `🔴0 🟡2 🔵2`** → 修复轮(**F2 = 父侧设计前提错误**:`AC-9b` 钉的是**本机 checkout 配置**而非仓库属性——本仓**无 `.gitattributes`**、**提交进库的 blob 是 LF**,CRLF 只是本机 `core.autocrlf=true` 的投影 ⇒ 在默认配置的克隆上**一个字节未改的仓库就会红**;改为**环境无关**的「同档不混用 + 跨档一致」,并以**模拟全 LF 克隆 14/14 绿**验证)。 - **本批沉淀两条纪律**:① **交付文档面必须回读本批纪要的裁定清单做交叉自检**(本批 🔴 与批 7 的 4 条 🔴 同族,已升级为可执行动作);② **在 `core.autocrlf=true` 的机器上,「零改动」不能只靠 `git diff` 证明**——git 会在 diff 前归一 EOL,一次「只改行尾」的编辑会被吞掉;必须**另用字节级读取佐证**(审计员提出并示范)。 - **登记面**:R-10 `Never translate` 的 3:1 措辞差(已登记不修)· R-11 判定族的出现点分布(「契约单点 + 文本多处」)· R-12 吸收清单记载的重复症状已陈旧 · **R-13** `test/session-state.test.mjs` T6 的**既有时序 flake**(单独复跑 10/10 绿、其后全量连续 7 次绿;不在本批写域、无因果)。 - **测试**:`node --test` **413/413**(基线 399 + 本批 14)· 既有测试档零修改(唯一授权改动 = `test/guard-e.test.mjs` 的 T-E19 清单登记一行,用户 2026-09-13 裁定)· **四档提示词与 `lib/prompts.mjs` 一个字节未动**(经字节级核验)。 ## [0.15.0] — 2026-09-13 **批 7:可移植性三件(METHODOLOGY 退役 + 产品提示词去本仓指涉 + 判据单一权威)** - **总目标**:让本插件的**产品提示词不再绑定任何具体仓库**——评审的判据来源由**项目自己声明**(缺省时**显式降级**而非静默),提示词里不再出现本仓/上游的文件名与路径,且「这个路径是文档还是代码」这件事**只有一个权威实现**。 - **① 退役(19 处,逐处改写,零整段删除)**:`injectMethology` 整函数删除(含其拼写错误的名字);`injectDocumentMap` **保留函数名、整段重写**(它此前读死路径 `docs/design/README.md`,**本仓不存在 ⇒ 今日恒不生效**);`advisor-design.md` 4 处 + `engineering.md` 9 处 + `advisor-msgs.mjs` 2 处判据/硬要求句全部改写。手法纪律 = **退役是改写不是删词**——不许留下空括号、双逗号、悬空限定语残句。**可验收形态**:`grep -ri methodology lib/` = **0**(含注释与提示词)。 - **② 声明键 + 三态显式注入(`standardsDoc` / `documentMapDoc`)**:两键均为**项目属性**(**global-only**,不做会话覆盖——与批 5 `contextTokens` 同族,实测最小面集一致,**免掉 7 个面**的改动)。三态**各有可区分输出**:声明且可读 ⇒ `## Project Standards` / `## Document Map` + 全文(**新增 16384 预算 + 截断标记**,此前无预算);声明但**读不到** ⇒ 响亮句并**点名配置键**;**未声明** ⇒ **显式降级句** + 明说「不得自行去磁盘找」。**注入门 = 声明键存在**,删掉旧的 `engineering === true` 门(留门会造出「已声明却被模式静默压制」的新静默洞)。**空串 / null = 撤销该键**(回到「未声明」这一**正常态**),设置页**清空文本框即发送空串**(不是「空字段不发送」,那会把用户锁死在已声明态)。 - **③ 判据单一权威(F17 之外的又一处「两份副本」)**:新建零依赖叶模块 **`lib/path-kind.mjs`** —— `isDocPath`(`docs/` 前缀 ∪ 文档扩展名,任意深度)为**唯一权威**;`isProductCodePath` = **纯补集派生**(无第二套规则体);`eng.mjs` 的 `isProductCode` 变**薄转发**(名字与 export 保留 ⇒ 写门禁字节夹具不需更新);`advisor.mjs` 本地 `isDocFile` **删除**;**`^src\/` 死锚删除**——**这是删死代码而非收紧**:对代码扩展名它冗余(`lib/`、`packages/app/src/` 靠兜底已真阳性),对文档扩展名它是**唯一矛盾源**(`src/README.md` 曾被写门禁拦却可进评审文档表)。上游那条「收紧为 `^src/` 前缀」是**记录在案的陷阱**,本批反向行之。**唯一行为变化**:`src/**/*.md` 从「产品代码」翻为「文档」——本仓无 `src/` ⇒ **零现场影响**。 - **流程留痕(本批一半价值在此)**:会诊 id 2 **4/4 交付**,且**三家独立指出父侧会诊任务书的四条前提错误**(`isProductCode("docs/x.mjs")` 方向反了 · 根级 `README.md` 实为文档 · 「`docs/` 两谓词语义相反」不成立 · 16K 截断归因错)——父侧回盘实测并落档;随后**两轮只读勘察**又纠正父侧两处(配置面是 **7 个面**不是「四面管道」;T-E19 的测试档清单闸 ⇒ 新增测试档会被拦)。设计评审 **轮次 1 = FAIL**(🔴5 🟡5 🔵4,**其中 4 条 🔴 是我违反了自己刚写下的裁定**)→ 修 → **轮次 2 = PASS**(5 条 🔴 经引用级复核全修;残留 7 条为「同一件事有第二个落点」)→ 实施(同步路径被 540s 内部截止 abort 且零输出 ⇒ 改**后台**一次通过)→ **独立分歧审计 🔴1 🟡2 🔵2**(🔴 = AC-P13 的静态锁自称六子点、实测只证 4 个,且一条断言**tautological**——**代码全部合规,是锁缺陷**;🟡 = 三条核心 AC 在服务端面零覆盖 + 卫生锁无套内锁)→ 修复轮(**11 次变异全部「删/插被保护行 ⇒ 目标用例红、不误伤」**)→ **交付代码评审 PASS**(🔵2)→ 收尾修复轮。 - **本批沉淀的四条纪律**(均已进交接页 §6):① **裁定写进纪要 ≠ 设计档兑现**(设计档交付前必须逐条回读纪要裁定清单做交叉自检);② **改了引用点,却没改被引用的副本**(每次改一个约定,必须全库检索其所有出现点并一起改——该 `grep` 级纪律原先只当**代码面**纪律用,**文档面同样适用**);③ **不许用「不可达」当免验理由**(「不可达」本身是需要证明的断言,本轮我那条证明就是错的);④ **当一条闸的谓词与它的意图不等价时,修的是谓词,不是被闸的东西**(本批两度命中:锚 B6「切片不含 engineering」vs「门不由模式决定」;评审 #2 的同类建议——且第二次出在**评审建议**上,故**评审的话也要回盘核验**)。 - **登记面**:R-5 六维/七维清单预先存在的漂移(系统提示列 7 维、硬要求句列 6 维)· R-6 `.thincoder/advisor.md` 沿用上游产品名 · R-7 `viz/*.html` 过时数字 · R-8 eng_coder 参数描述里的示例路径 · R-9 `cordis.patch.yml` 白名单注释已过时。 - **测试**:`node --test` **399/399**(基线 384 + 本批 15)· 既有测试档零修改(唯一授权改动 = `test/guard-e.test.mjs` 的 T-E19 清单登记一行 + 同处注释,用户 2026-09-13 显式裁定「按登记制扩展」)· 新增 `test/path-kind.test.mjs`。 ## [0.14.0] — 2026-09-13 **批 6b:守卫 E(在途窗口冻结 + 预闸拦截 + 结算侧指纹兜底)** - **总目标**:把「评审窗口内被审文档不得被改动」从**流程纪律**升级为**机制不变式**——窗口开启时**事前拦**(`write`/`edit` 撞冻结集 → 拒绝 + 真逃生门),窗口结算时**事后核**(签发前重算指纹,失配则本次不签发)。守卫 D 让**失败**自证来源;守卫 E 让**成功**自证依据。 - **闭合的问题(F17 型漏洞)**:令牌指纹此前是**派发前快照**、签发时**不复检** ⇒ 评审在飞期间(我方路由下可达数分钟到 1 小时)改动被审文档,评审员读到「前半段旧内容 + 后半段新内容」的混合体,而宿主照常按 `verdictPassed ∧ echoOk` 签发一枚**声称审过**该内容的令牌。**静默**且**每次窗口内编辑必然发生**。我方没有上游那种基于变更日志的 `reviewIsStale`,故用**已有的两口子**(在途槽位 + D-30 指纹)拼出等价物。 - **两半,缺一不成立**: - **预闸**(事前):新增**独立** `tools/pre-execute` 监听器 `makeDocFreezeGate`(`lib/eng.mjs`),命中冻结集 → `{kind:"deny", reason:"frozen: …"}`。**与写门禁刻意不同**的三点:① **fail 朝向相反**(已确认窗口内 fail-closed;写门禁无条件 fail-open)② **depth 不豁免**(写门禁 `depth>0` 放行——子代理是受令牌保护的实现者;预闸拦的是「正在被审的文档」,写入权与写入者身份无关)③ **拒域不相交**(冻结集 = 文档 vs `isProductCode` = 非文档)。拒文案前缀 `"frozen: "` **不与** `"denied: "` 混用(两态出路完全不同)。 - **结算侧兜底**(事后):`finalize` 的 `if (echoOk) {` 首条语句重算并集指纹,失配 ⇒ **不签发**。这一半专门覆盖预闸**原理上拦不到**的面(`bash`/`pwsh`/`run_code`/`file_ops`/外部编辑器/别的会话)。 - **槽位载荷扩容**:`inFlightJobs` 值 `{jobId, mechanism}` → `{jobId, mechanism, reviewType, docSet}`;`setInFlightJob` 加**第四位置参** `payload`(缺省 `{}`)⇒ 既有 **6** 个三参调用点(advisor ×2 / eng ×2 / escalate ×2)**零改动**仍合法。新增只读访问器 `designFreezeSet()`——**扫全表**而非按会话键单查(子代理 `session.id` ≠ 父会话 ⇒ 键查必然 miss,而子代理恰是典型漂移向量)。 - **闭包捕获**:`castDocPaths`/`castDocHash` 在铸造块末捕获、`finalize` 只读捕获值。与「读活 `state.pendingDoc*`」在当前代码下**等价**(单飞入口检查早于铸造,窗口内无第二写点),但捕获让正确性**不依赖**该不变式——否则将来有人在窗口内加一个写点,守卫会**静默退化成自比恒真**且无测试会红;捕获还把「不许读活 state」落成**机器验锚**(锚 A1:签发块内 `state.pendingDoc` = 0 次)。 - **漂移的处置语义(四条,缺一会引入新死锁)**:① **不签发**(不写 `state.designToken`、不调 `saveTokenRecord`)② **不撤销**既有令牌(漂移针对 pending 集;与「PASS 但回显缺失不撤销」同族)③ **不计三振**(早返回绕过通用计振尾——护栏计的是**插件自身健康度**,七 kind 无一例外是管线自己的故障;漂移是管线**正常**而外部改了文件,计入会让诊断面指向错误修法)④ **复位轮次**(`advisorRound=0` + 清 prior + 落盘)——否则下一轮取「只验证不找新问题」的**收敛轮**提示词去覆盖**从未被评审**的内容 = 真实的放行面。**不** `bumpAdvisorGeneration`(代际是「弃置在飞结果」的轴,+1 会误伤无关机制的 settle)。 - **真空豁免**:`castDocHash === null`(空文档集 / 铸造即不可读)⇒ 跳过复检、签发照旧(D-30 决策 D6 的既有权衡;漏判会让**空集评审永远签不出**)——AC-E20 专锁。 - **流程留痕(本批一半价值在此)**:会诊 id 1 **四家全部交付**(v4-pro / glm-5.3 / gpt-6-astra / kimi-k3)→ **五处分歧父侧裁定**(① 闭包捕获 vs 读活 state ② 子代理拦不拦 ③ **失配是否计三振** ④ fail 朝向扁平 vs 分层 ⑤ 其余三处)→ 设计评审**轮次 1 `FAIL`**(🔴 = 真空豁免无 AC;**评审员判得对**,纪要 §4 栽点 2 已明令「FR + AC」)→ 修 12 条 → **轮次 2 `PASS`** → 实施 → **独立分歧审计 🔴0 🟡1 🔵5**(审计用 **10 组变异实验**自证:M8 删 `.slice()` / M10 删空集跳过各自 **22/22 存活** ⇒ F1 两条已规定不变式零覆盖)→ 修复轮(+`T-E22`/`T-E23`,**变异自证可失败**:M8→23/1 唯红 T-E22、M10→23/1 唯红 T-E23)→ **交付代码评审 `PASS`**(🔴0 🟡1 🔵4,含 F3 的调用点计数订正与 §12.6 的**设计文本缺陷裁定**)。 - **父侧自身缺陷亦落档**:① 派发任务书把 stage 1 的 check 写成**不存在的** `test/in-flight.test.mjs`(实现者按纪律换等价自检并如实上报,未为迁就任务书造文件)② 设计档 §6.1 伪代码把 `const` 画在块内却又要求块外消费——**按字面不可运行**(判定为**设计文本缺陷**,正文冻结不改、以 §12.6 #1 裁定为准)。两次都属「凭形状推测而不回代码核验」的同一类病,与批 6 §0.2 同族。 - **登记面(本批不做)**:O-E1 跨会话误拒 · O-E2 跨链过度冻结(D-30 并集语义投影)· O-E3 未过期旧令牌 + 漂移在 `eng_coder` spawn 时不复检(F17 类洞、**窗口外**,属 D-30 扩展)· O-E4 `file_ops` 条件纳入 · O-E5 路径 cwd 偏斜(miss 方向) · O-E6 不可读原因不细分 · O-E7 若出现「每窗必漂」则另立**独立漂移计数器**(与三振正交),不塞进三振。 - **测试**:`node --test` **384/384**(既有 **360 原样全绿**,既有测试档零修改;新增 `test/guard-e.test.mjs` 24 用例覆盖 AC-E1…AC-E21 + 机验锚 A1…A10)。 ## [0.13.0] — 2026-09-13 **批 6:死亡可诊断(守卫 D 判定换血 + 四家族 abort 溯源 + 裸 abort 灭绝)** - **总目标**:让**每一种死亡自证来源**——「哪一层按下的(provider / agent / settle)+ 因为什么(user / timeout / cancel / stop / unknown)」在一次工具返回里可判。动机有**生产事故实证**:`consult.mjs` 头注自认「2026-09-08 生产:**20 次失败全被读成「aborted 无死因」**」(D-28 当时试了 21 次才诊断出来)。 - **新模块 `lib/abort-provenance.mjs`(唯一词汇表)**:`TRIGGERS`(5) / `LAYERS`(4) / `triggerOf` / `abortError` / `timeoutError` / `annotateAbort` / `deathLine`(截断顺序 = 先截 detail → 再拼 cause/tag → 最后整体兜底 ≤300)/ `abortTag`(`detail` 机器短标签)/ `hostAbortInfo` + `hostAbortSource`(宿主面机械判据:控制器已中止 ∧ reason **未带**我方标注 ⇒ `{cancel, settle}`)。 - **FR-AP2 墙判定换血**:`runAdvisorToolLoop` 的 `/deadline reached/` **文本嗅探整行删除**,改由 `deadlineFired` latch 与 `abortInfo.trigger` 判定——适配器以**无声样 AbortError** 抛出时不再被误判为「用户中断」并丢掉超时统计尾。 - **FR-AP3/AP4**:`shouldBudgetNudge` 纯函数 + 循环顶**每场至多一次**的 75% 预算提示(走 `messages` 注入,**不进**返回正文/prior);超时尾**纯追加**——既有那句一字符未动,其后追加 `tool calls / review text produced / budget` 三要素与两条出路。 - **FR-AP5/AP6 四家族接线 + 裸写点灭绝**:advisor 五站(循环 catch · 环内 `kind==="error"` 站 · 两条 job catch · 外层 catch)· eng 三站 · escalate 三站 · consult 五站;**`grep '\.abort()' lib/` = 0 命中**(基线 5 处裸写点全部补上 reason 载荷),**零新增写点、零定时器时长/条件变更**(`.abort(` 计数与 HEAD 逐文件相等)。 - **流程留痕**:设计评审**轮次 1 = FAIL**(🔴 = 我自己的设计档自相矛盾:超时尾「追加」与「替换 + 逐处改断言」三处打架,而零改面纪律禁止改断言——**评审员判得对**)→ 修正为纯追加 → 轮次 2 PASS → 实施 → **独立分歧审计 1🔴/4🟡/3🔵**:🔴 = consult 的 stop/cancel 两构造的判据打在**手搓等价物**上、且生产死亡行**没有消费者**(`settleChild` 丢弃 payload)→ 修复轮**给它接了真实消费面**(死亡行进该行模型的可见文本),并给出「删生产站点即红」实测;其余含宿主面层映射(job_kill 误报 `unknown@agent` → 修成 `cancel@settle`)、环内 error 站补接线、矩阵补六站、模块实测表计数订正、定时器门禁改逐字比对、两个不可持久的锚(**D-37**/T-G9 与 T-AP9)改成历史事实锚。 - **测试**:`node --test` **360/360**(既有 320 中 319 条原样未动 + T-G9 按 D-37 授权修为历史锚;零其余既有测试修改)。 ## [0.12.0] — 2026-09-13 **批 4:设计评审豁免 5 轮上限 + 三振结算护栏(成对吸收)+ D-35 评审链作用域折入** - **总目标**:把设计评审的**拒绝轴**从「轮次上限」换成「结算健康度」,且两者**成对**生效——cap 只对代码评审生效;设计评审由三振护栏兜底。两半拆开任一件都比现状更糟(只豁免 = 撤掉唯一的界、无限烧钱;只护栏 = 第 6 次仍被 cap 误杀,吸收等于没做)。 - **FR-G1 豁免**:cap 检查条件加 `reviewType !== "design"`(`MAX_ADVISOR_ROUNDS` 与 **cap 消息体逐字节不变**);轮次照增、提示词轮换与显示照常——豁免的只是「第 6 次拒绝」这一轴。 - **FR-G2 三振护栏**:同一文档集连续 3 次跑不出可用结算 ⇒ 后续发起被拒(**零 LLM、零状态变更**;工具层 + 核心层**双入口**);键 = **纯路径形状**(`normalizeDocPath` 归一 + 去重 + 排序;空集哨兵 `"empty"`;**绝不用内容哈希**——那会被一次空白编辑洗白);计数 = **会话级内存**(照抄 `codexFailureCount` 先例,不落盘);清理只挂 `session/disposed`,**不挂** `resetRouteFailureState`。 - **FR-G5/FR-G6 计数与复位**:宿主截断族 + 陈旧结算 + 凭证落盘失败 + 无报告结算计振;`interrupted` **不计**;派发返回 **不计**;**dsh 后台兜底 abort 按 `timeout` 计**(机械事实,不做串面猜);**codex `ABORTED` → `interrupted`(0 振)**、`TIMEOUT` → `timeout`(+1);可用判决(pass ∧ 回显 ∧ 落盘成功)与显式 `VERDICT: FAIL` **复位**(FAIL 同时撤销 token 是**另一条正交轴**,注释钉死)。 - **FR-G9(D-35 折入)**:设计评审链**按文档集作用域**——换文档集即等价类型切换(round/prior/代际重置),与 F11 合并为**单一重置谓词**、重置动作各执行一次;`session-state.json` 新增可选字段 `lastDesignDocKey`(**本批唯一 schema 变更**)。该字段存 `sha256Hex(designDocKey)` **摘要**而非路径原文——`session-state` 的既有 AC-20 契约禁止落盘被绑文档路径,父侧裁定接受(等值判定语义等价)。 - **流程留痕(本批一半价值在此)**:设计评审 **PASS**(12 条发现同链处置)→ 实施 → **独立分歧审计 2🟡/1🔵**:① 工具层预检**不是「摘除即红」**(核心层单独就满足全部四条断言——审计员用「连 `deps.llm` 都没有」的探针实证)② **codex 路由上的用户中断被计振**(`ABORTED` 信封未映射 ⇒ 归 `review_failed`,与 US-4/README 矛盾)③ `stale` 对**失败**的 job 也记账 → 修复轮三条全修,并各做「**还原即红**」实验(A 摘工具层→T-G3b 红;B 摘 ABORTED 映射→T-G5g 红;C/C3 还原 stale 打点→T-G5f 两臂各自红),实验后逐字还原。 - **D-35(评审链会话级 → 文档集级)**:本批开工时**现场复现**——同一会话里第二份设计文档拿到的是**收敛轮**提示词(「只验证、不找新问题」)、prior 还是**另一份文档**的发现,并据此**铸造了一枚声称覆盖未评审文档的令牌**。该次评审被判**无效、令牌不予采用**;缺陷登记为 D-35(🔴)并折入本批(同一把键)。 - **测试**:`node --test` **340/340**(既有 320 **原样全绿**,零既有用例修改)。 ## [0.11.0] — 2026-09-13 **批 5:评审上下文预算跟随模型窗口 + 评审估算器 CJK 加权(120K 硬编码退场)** - **总目标**:让评审循环的上限来自**它真正要调的那个模型**,并让估算值不再对中文内容系统性撒谎——两件同批落地(拆开任一件都比现状更糟:只上窗口派生会 CJK 真实死点推到越过真窗、只上估算器会让 CJK 死点 4× 前移)。 - **FR-CB1 窗口三级链**:`advisor.contextTokens`(手配)> `ctx.llm.resolveModelInfo(provider, model).context.contextWindow`(运行时权威——**本插件早已为 effort 在调同一方法**)> 保守兜底 `131072`。逐级 fail-open;兜底/元数据失败时 `console.warn` + post-finalize 追加一行(值 / 来源 / 两档 / 出口),**绝不进判死文案**。 - **FR-CB2 比例式两档**:`limit = floor(窗口 × 0.8)`、`compactAt = floor(limit × 0.8)`(两次 floor 钉死)。本机默认评审路由(`deepseek-flash`,适配器声明 **1M**)判死线 120000 → **800000**;128K 级 / 未知路由 120000 → **104857**(收紧 12.6%,给「对该路由一无所知」一个诚实的头寸)。`MAX_CONTEXT_TOKENS` **退役、不留别名**。 - **FR-CB4 估算器 CJK 加权**:逐消息 `ceil(ascii码元数/4) + 非ASCII码元数`——纯 ASCII **逐值等于旧式**(可机验零回归),`"中"×400` 从 100 修正为 **400**。签名与导出面不变(仍为模块私有)。 - **FR-CB5 三面同步**:`advisor.contextTokens` 进 PUT 白名单(整数 16384..4194304,校验单一事实源在 `lib/advisor.mjs`)⊕ `mergeGlobalConfig` 白名单透传 ⊕ 设置页卡片(占位不写具体数值 / 回显仅配置层 / 含残余格子提示 / 内联校验 / 清空即删键 / **不新增服务端 GET 载荷**)。 - **流程留痕(本批的价值一半在这里)**:设计评审 **PASS**(7 条发现同链处置)→ 实施 → **独立分歧审计抓到 1 条 🔴 假锁**——T-CB4 的 CJK 判据全部打在测试档**本地副本**上,把生产权重改成任意 `w ∈ (0.552, 1.504]` 仍 **319/319 全绿** → 修复轮新增 **T-CB10 走生产路径**(`"中"×300000` ⇒ 逐字 `300007` 判死;把权重改 0.75 即转红,红→绿两次实测留档)→ 交付代码评审 **PASS**(2 🔵:模块头注释枚举滞后、逐调用配对锁退化——均已修)。 - **测试**:`node --test` **320/320**(既有 304 **原样全绿**,零既有用例修改)。 - **同批的流程性修正**:批 3 交付时 `package.json` **未 bump 版本**(`docs/README.md` 该行却写了 `v0.11.0`)——本批统一为 **0.11.0** 并修正那条声明。 ## [0.10.0] — 2026-09-11 **D-30 design token 生命周期:续期路径 + 精确切除签名腿(批 2)** - **总目标**:把 design token 从「限期作废、只能靠再评审重铸」改成**授权与时限解耦**——只要被评审的设计文档集没变,就续期而不是重评审;文档变了才必须重评。 - **FR-T1/T2/T3 两段化 + 删密钥链 + 审批码换源**:token = `:`,**恰好两段**(对齐上游 thincoder 的「uuid:expiresAt, exact slot match + TTL only」);D-25 引入的 HMAC 签名腿与整条密钥解析链(`resolveTokenSecretPath`/`resolveTokenSecret`/进程内单例/公开默认值回落)**整体删除**。删除依据(需求档 §2.3 威胁模型,会诊判定 + 父侧回代码核实):① 门禁刻意 fail-open(`eng.mjs:167`)——它是纪律护栏不是安全边界;② 真正挡住伪造的是**记录全等匹配**(判定在签名校验之前);③ 密钥与 token 镜像**同目录**(能写后者者几乎必然能读前者,Windows 上 0600 近似 no-op);④ 净效果为负(密钥链换来启动全量作废 + 一条独立运维失效轴,防伪造增量 ≈ 0)。**审批码不一起删**(否则 R2 的 `[APPROVE:]` 防伪造语义静默归零)——改为**无状态派生** `sha256(uuid).slice(0,8)`:uuid 由宿主 `randomUUID()` 生成且**从不进提示词**,对主代理同样不可预测。 - **FR-T4 缺省 TTL 7d**:`TOKEN_TTL_DEFAULT_MS = 7d`(TTL 只作陈旧度护栏);`cordis.patch.yml` base 的 `engTokenTtlMs` 3600000 → **604800000**,使新缺省在交付后即刻现场生效(决策 D11)。 - **FR-T5 续期(本批核心)**:落点 = **`eng_coder` 过期子分支**(决策 D1;否决写门禁——其契约是 fail-open 而续期必须 fail-closed,且没有回传新串的通道;否决 advisor 入口——语义倒置,续期正是为了不付评审成本)。四路判定:无记录/无指纹 → 无法续期;文档不可读 → 视为已变更拒绝(fail-closed);指纹同 → **同一 uuid** + `expiresAt = now + 当前生效 TTL`,内存与磁盘同步并**回传新令牌串 + 「替换副本」指引**;指纹异 → 文档已变更拒绝。指纹 = 文档集**路径 + 内容**双绑:`sorted(normalize(path) + "\0" + sha256(content))` 再整体 sha256(新建 `lib/doc-hash.mjs` 单点实现;`normalize` = `path.resolve` + 反斜杠归一 + **不做**大小写折叠)。快照时点 = **待批令牌铸造时**,内容 = **历轮 documents 之并集**(决策 D5 + 设计评审 #2:防止「批准轮收窄 documents」把指纹绑窄);空文档集不写指纹(决策 D6);**未过期路径不做文档校验**(决策 D7,代价显式接受)。 - **FR-T6/T9 文案与迁移**:过期提示四分支各自说清(已续期 / 文档已变更 / 无法续期 / 旧版本令牌);写门禁 deny 在令牌**已过期**时不再说「先写设计文档」(误导)而是指向续期/重评;TTL > 24h 时有效期显示**日期**而非裸 `HH:MM`。三段式旧令牌由形状检测识别 → 明确「旧版本令牌、形状已不兼容、无法续期」并指引重跑一次(**不做**旧格式兼容验签——保留旧通道等于保留伪造面,先例 = v2 废除 `[DESIGN-TOKEN:...]` 回显,决策 D8)。 - **FR-T8 token-store 两缺口**(会诊 G1/G2,父侧实证):① 保存路径此前显式重建三字段 → 传入的 `docHash` 被静默丢弃(续期永远拿不到素材),现补 `docHash`/`docPaths` 条件字段;② 全局清扫此前「过期即删」→ 会顺手删掉**别的会话**可续期的记录(头号场景「重启后续期」非确定性失败),现改为五分支:畸形删 / 未过期留 / 过期且无指纹删 / 过期且有指纹**保留**(保留期上限 = `ENG_TOKEN_TTL_MAX_MS` = 30d,锚点 = `expiresAt`)。磁盘格式**纯追加**,旧记录读得动。 - **FR-T7 会诊口径修正**:`lib/prompts/main.md` 原称会诊「绑定当前回合、打断或回合结束即终止」,与 `consult.mjs`(D-28)实现矛盾——改为「**跨回合存活**(打断/回合结束不杀),且**有界**(只由 `consult_stop` / session 销毁 / `consultTimeoutMs` 看门狗终止;缺省 10min 可配)」。 - **删除后的两处静默显式处理**(设计评审 #9):① 旧密钥环境变量 `THINCODER_TOKEN_SECRET` 变为无声 no-op → 启动时打印**一次性弃用告警**(密钥链已删除、该变量不再有任何作用);② 磁盘上的 `.thincoder/token-secret` 成孤儿文件 → **声明不清理、留档**(删除属破坏性动作,不在本批范围;README 已注明其已被弃用)。 - **本批无 UI 变更**(设置页字段已在 D-29 落地);零新增依赖、无构建步骤。 - 测试:全量 `node --test` 全绿(详见提交时套件计数)。 **D-29 配置面缺口:`consultTimeoutMs` / `engTokenTtlMs` 进 user 层白名单(用户实测痛点)——同批发版** - **缺口**:两键运行时都读得到配置(`lib/consult.mjs:115` / `lib/advisor.mjs:677`),却都被 `mergeGlobalConfig` 的白名单排除 —— 结果是它们**只能**在 entry base(`cordis.patch.yml`)里改,**设置页改不动**。用户侧实测后果:① design token 每 1h 过期,而新 token 只能由一次新的设计评审签发 → **反复重评审**;② 会诊里慢/挂的模型把整轮吊满缺省 600000(10min),四个模型常只剩两个能回。 - **修复**:两键补进三面白名单(对齐 `dshBackgroundTimeoutMs` 的 N-5/US-10 先例)——`lib/config-store.mjs` 值域校验与常量(`isValidConsultTimeoutMs` 30000..3600000 / `isValidEngTokenTtlMs` 600000..2592000000)⊕ `mergeGlobalConfig` 白名单 ⊕ `lib/index.mjs` PUT 校验(`topAllowed` + 区间错误文案)⊕ 设置页表单字段(`lib/client.js`:校验 / 载荷 / 初始态 / busy 窗口编辑保留 / 新增卡片)。 - **base 示例值**:`cordis.patch.yml` 的 `consultTimeoutMs` 600000 → **1800000**(慢模型有合理窗口;原值会让整轮吊满 10min)。`engTokenTtlMs` 保持 3600000 —— **缺省值的语义变更属后续批次,本批只让两键可配**。 - **测试**:新增 4 条(`test/codex-runner.test.mjs`)——① PUT 接受合法值 + 回归断言「不再被当 unknown top-level field 忽略」+ merge 覆盖语义;② 区间外/非整数拒绝且不落盘(边界值两侧合法);③ 设置页接线静态核对(`client.js` 无既有 UI 测试面,锁住「后端开了前端没接」这类静默半成品:初始态 / 载荷 / 校验 / busy 保留 / onChange / 区间常量同值);④ user 层落盘 → `loadUserConfig` → merge → 到达运行时消费点的完整往返(锁「user 层能覆盖 base 钉死值」)。241 → **245 全绿**。 - **文档同步**:`README.md` 的 user 层白名单段与 `cordis.patch.yml` 头部白名单清单同步补齐(两处此前仍称两键「只在 base 配」,与本批改动矛盾);README base 示例值同步为 1800000。 ## [0.9.2] — 2026-09-09 **D-28 会诊跨回合失效修复(生产缺陷:PTC run-scoped 信号被继承)** - **根因**:`consult_start` 把调用方 `exec.signal` 直传子代理,而 PTC 模式下它是 run_code 程序的 run-scoped 控制器(`dsh-tools/lib/types/ptc.js:375` 注入、`:532` 在程序 settle 的 finally 里 `abort('run_code settled')`)——会诊是「本回合 start、后续回合 check」的跨回合协议,子代理因此在启动 1~3 秒内被 `child.cancel({kind:'parent'})` 杀掉(stopReason=aborted,且从未发出模型请求)。2026-09-08/09 lore 会话生产实证:21 次尝试 20 次失败;唯一成功的一次是启动程序被一个 61 秒的 grep 卡住没结束。 - **修复**:子代理改用插件自持 `AbortController`(`lib/consult.mjs`)——取消只走 `consult_stop` / `consultTimeoutMs` 看门狗 / session 销毁 `cleanupConsultSessions` 三条显式路径;`lib/index.mjs` 的 consult_start 不再传 `exec.signal`。 - **附带归因修复**:看门狗超时与显式 abort 在驱动层都坍缩成 `stopReason=aborted`,现按 `timedOut` 归因(超时不再被读成「aborted 无死因」);预算 < 1min 时显示为秒。 - 新增 `test/consult.test.mjs`(跨回合信号解耦回归 / stop 早停 / 看门狗有界 / session 销毁清理 / 选择器语义)。 - **advisor 复审跟进**(1🟡+5🔵 同轮清零):①🟡 `consult_check` 的调用方信号只结束本次读取,不再杀子代理(此前会落进 failed 分支被读成「child ended: aborted」),并写进头注释;②🔵 codex 行提前 return 漏掉 `clearTimeout(watchdog)`,已补 finally;③🔵 codex 行 ABORTED 也按 `timedOut` 归因;④🔵 fire-and-forget 子代理补 `.catch` 兜底(异常不再让 `pending` 永久 >0 卡死后续 check);⑤🔵 亚秒预算不再显示 0s;⑥🔵 `consult_check`/`consult_stop`/advisor 补齐 `markSessionSeen`。 - 测试 235 → **241** 全绿。 ## [0.9.1] — 2026-09-07 **R6 维护轮(D-26 十项打磨全清,登记表 27/27 终态)** - eng/escalate codex 分支:`resolveCodexCliGlobals` 告警逐条 warn + timeout/idle 非法回落响亮告警 - `engineeringToggle` 移除未用参数(调用方与测试全部迁移,兼容 shim 删除) - eng 单飞拒绝返回补 warnPrefix;codex/dsh 空输出统一「(empty report)」交付语义;F10 盘回填区分「已签发未传」与「从未签发」两态文案 - eng dsh 同步路径簿记收敛到模块级 `deliverBookkeeping`(四路单一实现) - dsh 后台兜底超时信封 code=ABORTED(resolve-race 与 reject-race 双分支均触发回滚指引) - escalate codex jobs reject 分支补 `codexThreads` 清理;eng/escalate 后台派发句柄并入配置告警(warnPrefix,对齐 advisor 先例) - 测试 229 → **235** 全绿 ## [0.9.0] — 2026-09-06 **R5:dsh 路径后台化(DP-1 方案 B)——插件自建后台机制覆盖全部四机制,DSH 平台零修改** - **advisor dsh 循环自动后台化**:dsh 路由预算 > `codexCli.budgetCapMs` 且 ctx.jobs 可用 → 自动派后台 job 跑完整评审循环(job 内预算不钳制);≤cap 或 jobs 缺失 → 同步 + D-17 钳制告警(同步快路径护栏保留) - **escalate/eng dsh 显式 `background` 参数**:不传=同步(现状);传=true → 后台 job 内跑子代理(完成通知送指针、全文经 job_output);followup 同参数语义 - **挂死兜底**:新配置 `dshBackgroundTimeoutMs`(顶层,默认 30min,60s~60min;三面白名单 + 设置页字段);deadline 合成 = max(任务预算, 兜底) - **降级两段式**:派发前预检/抛错 → 同步+钳制+告警;run() 内 ctx 破裂 → job 诊断失败(不可恢复) - 单飞复合键、代际检查、簿记-done-成功分支全套复用 R2 模式 - 工程流程契约:background 任务返回句柄,「等完成通知再继续」 - 测试 215 → 229 全绿;真机终验:dsh 路由 900s 预算自动后台化、钳制告警消失 ## [0.8.0] — 2026-09-05/06 **R1-R4 机制缺陷治理(26 项登记缺陷:24 修 1 驳 1 测量收口;测试 125 → 215)** - **R1 诊断与校验层**:effort 最近支持档回落(等距向上取,全 9 消费点收口含 codex catalog resolver);advisor 流观测(finish.kind/usage/blocks)+ 空响应四形态分类;dsh 预算钳制;`advisor.maxOutputTokens` 可配置(三面白名单,默认 16384) - **R2 执行架构与进程生命周期**:escalate/eng codex 路径迁 ctx.jobs(簿记 done 成功分支恰好一次);三机制复合键 single-flight;`advisorGeneration` 代际检查(晚到 finalize 不复活已重置状态);全局 codex 并发准入 `codexCli.maxConcurrent`(默认 8,CONCURRENCY_LIMIT fail-fast);启动清扫陈旧临时目录;DP-1 方案 A dsh 钳制 - **R3 回落与失败语义**:回落活锁封顶(fallbackFailureCount 连败 2 次硬停 + 双路由诊断);空响应自动重试一次(D-裁决-1)→ 仍空转可归因失败不烧轮次;prior 纯净化(机制后缀不进 lastAdvisorOutput) - **R4 一致性与 UX**:设置页保存竞态修复(busy 全表单禁用 + 触碰字段保留);`advisorOverride.runner` 三面同步;**token-secret 安全加固(D-25)**:公开默认密钥 → env / `$DSH_HOME/.thincoder/token-secret` 持久化随机密钥(0600,损坏自愈)/ 无持久化面响亮告警;D-15 idle 测量(3 次 high-effort 实测最大静默间隙 50s << 240s,维持 300s 默认) - 真机验证 10 项收口(四机制 + 两受害场景:空响应与 effort 秒死活体治愈) ## [0.7.0] — 2026-09-05 **codex-cli runner 集成(一/二期 + 评审修复 + jobs 派发 + 智能回落基线入库)** - `lib/codex-adapter.mjs`:codex exec 子进程适配器(无 shell spawn、Windows shim 解析、进程树终止、--json 事件流、-o 权威输出、idle watchdog、B8 反套娃前缀) - 四机制接入:advisor/consult 只读沙箱、escalate/eng_coder 写沙箱(workspace-write);followup 线程连续性(`exec resume`,内存态) - ctx.jobs 后台派发(预算 > budgetCapMs):完成通知指针 + job_output 全文(保尾截断);job id branded string - 智能回落:codex 连续 2 次失败 → 自动回落 dsh 路由一轮;`codexCli.budgetCapMs`(默认 540s,平台 600s 墙钟内安全余量) - 设置页:runner 选择、codex 模型/effort 目录化下拉(`/catalog` 运行时注册表端点)、budgetCapMs/maxConcurrent/idleTimeoutMs 字段 - 测试 78 → 125 全绿(本版本为历史工作基线入库,版本号自 0.1.0 锚定) ## [0.6] — 2026-09-03 - **F12 会话级状态持久化**:`$DSH_HOME/.thincoder/session-state.json`(engineering/评审轮次+prior/触达文件/覆盖,跨重启恢复、只填空槽、7d TTL、与 design-tokens.json 分文件回滚独立) - **F13 eng_coder 阶段化任务书**:`stages` 结构化参数(maxItems 10、四段渲染、阶段纪律、stage 状态表前置防掐断) - compactMessages keyFiles 去重上限 15;测试 50 → 78 ## [0.5] — 2026-09-02 - 设置页 UI 打磨:宿主语义 token(深浅色随动)、code review 2 轮 11 项修复、池保存/草稿播种修复 ## [0.4] — 2026-09-02 - **二期设置页**:全局配置二层(entry base ⊕ `$DSH_HOME/.thincoder/config.json` user 层,保存即生效);DSH 设置面板「Thincoder」页(手写 CJS 免构建);host config API `/thincoder-suite/api` ## [0.3] — 2026-09-02 - **F10** design token 磁盘持久化(重启免重评审);**F11** reviewType 切换重置轮次;thincoder-eng 预设 PTC 工具集;F8 判定启发式 v4 ## [0.2] — 2026-09-01/02 - **一期 host 机制**:advisor 分层路由(round1/convergence)、effort 透传、会话级覆盖(advisor_config)、预算模型+超时硬生效;F8 评审通过判定修复;F9 eng_coder 资源策略 ## [0.1] — 2026-09-01 - thincoder 四机制(advisor/eng/escalate/consult)移植为 DSH cordis 插件(零依赖、免构建)