# 2026-10-06 工作日志(本工作区 `ai1net-dsh-server`)
> 承接 `2026-10-05.md`。本日主线:**看板改名 + 队列按目标过滤**(用户逐字驱动)+ 撤销上轮对 `agent-product` 的误建。
---
## 三十四、看板改名「检查程序」+ 队列按目标过滤(不再历史累积)
### 用户逐字(两条)
1. `看板中 常驻程序 改为 目标检查,里面的队列 应该是随着目标的不是目标累积的`
2. (答我反问,**纠正我复述的"目标检查"**)`没有常驻程序的说法 ,改为 检查程序`
⇒ ⚠️ 正名是 **「检查程序」**(⛔ 不是"目标检查")。我复述错了一次,被当场纠正。
### 一、改名:根因是「角色名 ↔ 运行形态」混成一个词(同族第三次)
- **病根**:「常驻」是**运行形态**(`--supervise`),**不是角色名**。把形态当角色 ⇒ 名字里没有"它是干什么的"。
- **同族前科(同一类错,别只记这一条)**:
- 2026-10-03:我把「上报」改成「常驻」,用户驳回逐字
「**怎么又把上报 改成常驻了 常驻什么,不是 协作程序常驻吗**」。
- 本轮:用户又要求把「常驻程序」改掉 ⇒ **同一个词第三次被嫌**。
- **判据(以后选名先过这一条)**:名字必须答"**这个位子负责什么**",⛔ 不答"它怎么跑的"。
- **改的范围**:**只改用户可见文案**(标题/tooltip/chip/说明区/`aria-label`/peer 说明),
⛔ **代码标识符一律不动**(`prog`/`rt.prog`/文件名)—— 改了会牵动判据与存储。
### 二、队列按目标过滤(这是真缺陷,非文案)
- **病根**:`tasks.json` **只增不减** ⇒ 看板那格报的是**历史累计**,读者看不出「当前目标在跑多少」。
- 🔴 **机制自己早就知道**:`goalctl.py:577` 的「静默陷阱」注释逐字「**判据不绑目标**」——
那次**只警告没修**(怕动存量数据)。⇒ 本条是**同一病根的正面修法**,⛔ 不是新发现。
- **修法(四处,缺一不可)**:
1. `collabd.goal_fp()` —— 目标指纹 = `sha1(title)[:6]`
(与 `goal_dir_name()` **同源** ⇒ 目标目录名尾部的 `-xxxxxx` 与指纹**逐字相同**,三处可对着读)。
2. `task_report()` 给新条目 `setdefault("goal_fp", goal_fp())` ——
⛔ **只打新条目**;存量**不补**(补 = 把历史条目标成"当前目标" = **伪造归属**)。
⚠️ `setdefault` 只写一次 ⇒ 同一条目后续上报**不改归属**(防"跑到一半换目标 ⇒ 整条倒戈")。
3. `board._queue(tasks, st, cur_fp)` —— 只把 `goal_fp == cur_fp` 的算进 `total`/`by`/`open`。
4. 无归属存量**单列 `legacy`**(看板显示「另有历史 N 件,不计入」),⛔ 不混进当前目标。
- 🔴🔴 **指纹必须从形参 `g` 现算**(⛔ **不许读 `INBOX/goal.json`**):
`_goal_block()` 也渲染 **peer 格**(别的**工作区**的目标)⇒ 读本区文件 ⇒ 用**本区指纹**过滤对方台账
⇒ **对方格队列恒为 0**(跨工作区**静默**错,界面上看不出)。
✅ 实测:`vibe-product` 格 `fp=5d27fe`(**对方**指纹)—— 若串区会显示本区的 `c8154d`。
- **空指纹语义**:`cur_fp` 为空(未声明目标)⇒ **如实把全部当 `legacy`**,⛔ **不假装"队列为空"**
(那是把"没有归属信息"说成"没有活")。
- ⚠️ **`cur_fp` 的插入位置踩了两次坑**:① 插进**字典字面量**里 ⇒ `SyntaxError: ':' expected after
dictionary key`;② 插到 `build()` 的 `warn = []` 之后 ⇒ **函数归属错**(`_goal_block()` 的 `warn`
是**形参**、没有 `warn = []` 这行)⇒ 正解是 `_goal_block()` 内、`return {` **之前**、
`_lb = _labor(...)` 之后。
### 三、验收(都跑过,非声称)
- `selftest`:**PASS 97 / FAIL 0**(改前 95/2);`install.py --verify` **✅ 全绿**;`--manifest` 56 份/语法失败 0。
- `_queue` **四情形单元验证**全过:当前目标命中(total 4/legacy 3)|另一目标(total 1/legacy 6)|
空指纹(total 0/legacy 7)|无匹配(total 0/legacy 7)。
⚠️ 第一版**自己写错了期望值**(把 `open` 当成 "pending+running")⇒ 实测 `open = pending+running+blocked+other`
⇒ **是测试错,不是代码错**(差点误修代码)。
- 看板重启后 `board.json` 实测:本区 `fp=c8154d total=0 legacy=1`;`peer` 格 `fp=5d27fe`。
⛔ **`board.py` 是代码改动 ⇒ 必须重启看板**(只有 `board.html` 才是每请求实时读盘、不用重启)。
### 四、顺手修掉两处 `selftest` FAIL(都是我自己引入的)
1. **技能里留了项目串**:`goal_fp()` docstring 写了工作区名/历史线名/`goal.json` 的 id 值
⇒ 判据「技能就是技能,谁用产生的文件放在他自己那里」报红 ⇒ 已**泛化**(留事实、去专名)。
2. **`pitfalls.md` 的 P0-86 重复了两份**(3157 行与 3305 行,**逐字相同**,仅差一个尾空行)
⇒ 删后一份;并把留下的那份从 **7.3 KB/密度 2.1** 压缩到 ~5 KB(**判据要点、变异表、教训一句不丢**)。
⚠️ 判据「经验不许写成流水账」的**红线是超长 + 低证据密度**(>6 KB 且 密度<4.0),
不是"长了就删" ⇒ 压缩时**先保要点**。
### 五、另一个坑:`board.html` 行尾被改成 CRLF(差点提交 1856 行纯噪音)
- 现场:`board.html` 工作副本 **CRLF**(1856 个 CR),而 repo `HEAD` 版是 **LF**,`core.autocrlf=false`
⇒ `git diff` 整个文件重写(3544 行)。
- ✅ 处置:**按字节归一化回 LF** 再提交(`b.replace(b'\r\n', b'\n')`)⇒ diff 从 3544 行降到 386 行(真内容)。
- 🔴 **判据(沿用既有铁律)**:**行尾只信字节级**(`b.count(b'\r')`),⛔ 不看编辑器显示。
⚠️ 提交前若见"整文件重写",**先查行尾**,别当成真改动。
### 六、提交
`5dc6ced session-mechanism: 看板改名「检查程序」+ 队列按目标过滤(不再历史累积)`
(4 files:`board.html`/`board.py`/`collabd.py`/`pitfalls.md`;⛔ **未推送**)
---
## 三十五、撤销上轮对 `agent-product` 的误建(详情见 `2026-10-05.md` §33,此处只记结论)
- 用户令逐字:`删掉 让那个会话自己建立`。
- 删了 4 项 + 1 项连带:`tmp/supervise-inbox/goal.json`/`交付物/任务图-会话协作自检.json`/
`执行会话/目标-agent-product-3e3182/`/`collabd-state.json` 的 `roles` 主会话登记/
配置里的 `taskgraph` 指针(删载体要连"谁指向它"一起查,否则常驻每 10 s 刷 `Errno 2`)。
- 备份:`ai1net-dsh-server/tmp/bak/agent-product-misbuild-20261005/`。
- **交接姿态**:常驻**故意没重起**(用户令"让那个会话自己建立")⇒ **我不再插手该工作区**。
---
## 三十六、⚠️ 技能总仓「提交/推送」发现**历史分叉**(未决 · 待用户拍板)
用户 05:0x 说「提交」⇒ 核实两仓后发现**技能仓不能直接推**:
| 仓 | 本地 | 远端 | 关系 |
|---|---|---|---|
| 工作区 `ai1net-dsh-server` | `746e4f2` | 有上游 | ✅ 正常可推 |
| 技能总仓 `workbuddy_skills` | `5dc6ced`(含 5 个未推提交) | `19101ac` | 🔴 **两边无共同祖先** |
- 🔴 **远端是"重建的干净库"**:远端唯一提交 `19101ac init: workbuddy_skills 重建,仅收录 session-mechanism`
⇒ 有人**把远端历史重写过**(`git fetch` 报 `+ fe6ce9f...19101ac master (forced update)`)。
- 🔴 **本地是旧的多提交历史**:`5dc6ced`/`e4cbd2f`/`935e423`/`4adc431`/`1eec42e` + 更早。
- ⇒ `git merge-base HEAD origin/master` **为空** ⇒ **无法快进、无法普通合并**。
直接 `push` 会被拒(non-fast-forward);要上去只能 **`push --force`(覆盖远端历史)**。
- ⚠️ **这是不可逆的对外动作**(`--force` 会丢弃远端那个 init 提交)⇒ ⛔ **不擅自决定,必须用户拍板**。
- ⚠️ 另:技能仓还有 **19 个文件的未提交改动**(515 增/692 删,非本轮产物,疑似别的会话或早前轮次在途)
⇒ ⛔ **不代它提交**(无边界扫进来=伪造别人的工作)。
- ⚠️ 推送用 **`git@work.alotbuy.com:maogeigei/workbuddy_skills.git`**(work 域名,非 github)
⇒ 与 `~/.ssh/config` 里的 github 密钥路由**无关**(别去翻那套)。
---
## 三十七、看板 tab 改「按创建时间」稳定排序(用户:「tab 的排列顺序 应该按照创建时间顺序来」)
### 为什么改(活跃度排序的真实毛病)
- 旧键 `tab_hot` = 该块**最近会话活动**距今分钟数(`age_min`),10-04 定的「最新在运行的排前面」。
- 🔴 **病根**:会话活动**每次协作都会变** ⇒ tab 顺序**自己会漂**。
用户脑子里认的是「第几个 tab 是什么」⇒ 位置一变就**要重新找**,且**无从判断为什么变了**。
- ✅ 新键 `tab_created` = `goal.json` 的 **`declared_at`**(目标声明时间)—— 目标建立后**不动**
⇒ 顺序**稳定**。
- ⚠️ **为什么不用 `topics_declared_at`**:两者多数相同,但 `declared_at` 是**目标本身**的建立时间,
`topics_declared_at` 是**任务类别清单**的确认时间(可能后补/缺失)。语义要取前者。
- ⚠️ **缺 `declared_at` 的块垫底**(`"~"` 大于任何 ISO 时间串),⛔ **不许猜**一个时间塞进去。
### 🔴🔴 本轮最大的坑:`declared_at` 被块**丢掉**了(排序静默失效)
- 块里的 `goal` **子字典只留 3 键**(`title`/`short`/`acceptance`,`board.py:1795`)
⇒ `declared_at` **不在里面**。
- 我第一版从 `_b["goal"].get("declared_at")` 取 ⇒ **恒为空** ⇒ 三块 `tab_created` 全是 `'~'`
⇒ **排序形同没做**,而"升序"判据**照样绿**(因为三个都一样,升序恒真)。
- ✅ 正解:在 `_goal_block()` 里**单独带出 `goal_declared_at` 字段**(从**形参 `g`** 取)。
⚠️ 从 `g` 取而非回读 `INBOX/goal.json`:peer 块传的是**对方**的 goal ⇒ 取到对方时间,正确。
- 🔴 **同族教训(第 N 次)**:**块是"精简过的视图",不是 goal 原样**
⇒ 任何"要参与逻辑的字段"都必须**显式带出来**,⛔ 不能指望它在块里躺着。
### 判据重写(附变异对照,证有牙)
- 夹具改成**时间乱序**(t2 10-01 最老/t0 10-02/t3 10-03),且**文件出现次序就是时间倒序**
⇒ 排序一旦失效,输出顺序**立刻不同**(⛔ 三个时间同序 ⇒ 判据恒绿,同族栽过)。
- 新增**真造第四块**(**没有** `declared_at`)⇒ 必须垫底且 `tab_i` 最后一位
(⛔ 不造这块 ⇒ 判据恒真 = 假绿)。
- 新增**防回退**判据:源码里 ⛔ 不许再出现 `tab_hot` 当排序键。
- ⚠️ **我自己写判据时取错层**(`short` 在 `goal` 子字典里,⛔ 不在块顶层)⇒ 判据**恒红**,已修。
⇒ 写判据也要先确认"字段在哪一层",⛔ 别凭印象。
- **变异对照**:A 排序 `reverse=True` ⇒ 红 ✅|B 排序键换回 `tab_hot` ⇒ **五条同时红** ✅|还原 ⇒ 绿 ✅。
### 验收
- `selftest` **PASS 97 / FAIL 0**|`install.py --verify` **✅ 全绿**|`--manifest` 56 份/语法失败 0。
- 真起看板实测:`i=0 本机协作(2026-10-01T22:29)` → `i=1 vibe-product(2026-10-05T18:04)` ✅。
- ⛔ **`board.py` 代码改动 ⇒ 必须重启看板**(走 `dsh-board-keepalive` 计划任务,⛔ 不手动裸起)。
- 提交:`5e77f0e`(技能仓,未推送)。
### ⚠️ 顺带记录:本机裸 `nohup python board-launch.py &` 活不过工具调用边界
- 实测:`nohup ... &` 起看板 ⇒ 打印了「看板已起」但**进程随即消失**(工具调用结束即被收走)。
- ✅ **唯一可行起法 = 触发计划任务** `dsh-board-keepalive`(`Start-ScheduledTask`)
⇒ 与记忆里「常驻起来只有两条路:计划任务 / 会话后台任务+stdout 重定向」一致。
---
## 三十八、评估 `dsh-task-board` 能否移植进「任务会话技能」当工作台(结论:⛔ 不能移植,但可**映射概念**)
用户问:`E:\github\dsh-web\packages\dsh-task-board` 能否把这个功能移植到任务会话技能里当工作台。
### 它是什么(实读,⛔ 非推断)
- **`@linxin666/dsh-client-ui-task-board` v0.4.4** —— **DSH(DeepSeek Harness)的 Web GUI 插件**,
`src/` 下 80 个 TS/TSX 文件、**20,825 行**。
- 挂载方式:`cordis.patch.yml` + `dsh.bundle.patch` profile 层 ⇒ **只在 DSH 宿主里活**。
- 🔴 **依赖面全是 DSH 私有 SDK**:`@deepseek-ai/dsh-tools`/`dsh-llm`/`dsh-api-gateway`/
`dsh-workspace`/`dsh-host-webserver`/`dsh-client-ui-slots`/`dsh-api-session-controller`… **20+ 个包**,
另有 `cordis`(DSH 的插件内核)。
### 结论:⛔ **不能"移植"** —— 宿主不同,不是"换个壳子"的问题
| 维度 | `dsh-task-board` | 本技能(WorkBuddy 侧) |
|---|---|---|
| 宿主 | **DSH**(DeepSeek Harness) | **WorkBuddy** |
| 插件内核 | `cordis`(`dsh.bundle.patch`) | 技能包 + `.workbuddy/collab/` 脚本 |
| 界面座位 | DSH shell 的 `sidebar.panellist` / `main` 座位 | 自建 `board.py --serve`(20099)+ 手写 `board.html` |
| 数据面 | DSH Host `$DSH_HOME/task-board/ledger-v2.json` | `tmp/supervise-inbox/{goal.json,tasks.json}` |
| 执行引擎 | DSH `/goal` 目标驱动器 + dsh-llm-verifier | `collabd.py` 常驻 + 排期拉会话 |
| 语言 | TypeScript/React 插件 | Python + 单页 HTML |
⇒ **零修改侵入 DSH 源码**是它的卖点,但**反向也成立**:脱离 DSH 它**一行都跑不了**。
移植 = **重写**(20k 行 TS + React 换成 Python/HTML),⛔ 不是迁移。
### ✅ 但它有**真价值**:几处设计可直接**映射**到本技能的短板
| 它的机制 | 本技能的现状 | 可映射性 |
|---|---|---|
| **Host 权威账本**(浏览器只是异步视图,关页面不停调度) | 已有同思想(`tasks.json` 是唯一权威) | ✅ 已一致 |
| **账本原子写**(临时文件 + 原子 rename + revision) | `tasks.json` 写法定过 | ⚠️ 可对照补 |
| **任务验收门禁**(`update_goal(complete)` 被宿主拦截,不信 agent 自述) | **本技能正缺这一环**(改完靠检查会话**读**,无"拦截完成声明") | 🔴 **最值得借** |
| **有界执行历史**(每任务只留 20 条) | `tasks.json` **只增不减**(本轮刚处理"队列按目标过滤",病根同源) | 🔴 **值得借** |
| **8 个 agent 工具**(`task_board_list/create/run/schedule`…) | 机制侧靠 `collabd.py --report` 等 CLI | ⚠️ 形态不同,思路可借 |
| **cron 调度器**(5 段 + IANA 时区 + DST 语义) | 本技能有 `automation_update` 排期 | ⚠️ 已够用 |
### 我的判断(一句话)
**⛔ 别移植代码**(宿主不同、语言不同、依赖 20+ DSH 私有包);
**✅ 借两个设计**:① **验收门禁**(不信自述、宿主侧拦截完成声明)② **有界历史**(台账不许无限增长)。
若用户要的是"**在 WorkBuddy 里有个像它那样的工作台**" ⇒ 那是**把现有看板(20099)做厚**,
⛔ 不是搬这个包。
### ⚠️ 待用户确认的语义分叉(属"功能语义分叉",须提报)
用户说"移植到任务会话技能中当作工作台" —— **"工作台"指哪个?**
- **A**:WorkBuddy 里一个**看得见、能点**的面板(=把现有看板做强)
- **B**:只是**借用它的任务/验收模型**(=改机制,不加界面)
- **C**:真要在 **DSH** 里用(那与本技能无关,装进 DSH 即可)
⇒ 三选一才谈得上下一步,⛔ 别猜。
---
## 三十九、新建「项目管理界面」(照 dsh-task-board 模式 · 目标=项目)— 已交付在跑
### 用户纠正我(逐字)
> 「**不动现有任务会话看板**,**不是让你整体搬**,是照的他的模式做一个 **项目管理的界面**
> 来管理 执行任务,**目标等于项目**」
⇒ ⚠️ 我上一轮把问题问成"要不要移植/怎么移植"=**问错了方向**:用户要的是**新做一个界面**,
⛔ 不是搬迁、⛔ 不是改现有看板。
### 交付物(新建,⛔ 现有看板零改动)
📁 `交付物/项目管理界面/`(**3 个文件**)
| 文件 | 作用 |
|---|---|
| `project-board.py` | 只读服务 + 映射层(`--serve 20100`/`--once` 打摘要) |
| `project-board.html` | 界面:项目下拉 + 四列卡片 + 点开抽屉 |
| `project-board-launch.py` | **启动器**(计划任务动作指向它,⛔ 不直起业务脚本) |
- **端口 20100**(现有看板 20099 **不动**);计划任务 **`dsh-project-board-keepalive`**(5 分钟判活)。
- ✅ 实测:20100 `HTTP 200`;`/api/projects` 返回 2 个项目/12 张卡片;20099 同时 `HTTP 200`(互不影响)。
### 🔴 映射关系(唯一真源 = 现有看板的 `/board.json`,⛔ 不重算)
| 它的概念 | 本机制 | 数据来源 |
|---|---|---|
| 项目 | **目标** | `goals[]`(`goal.short`/`tab_created`) |
| 工作流分组 | **任务类别** | `labor[]` |
| 卡片 | **台账条目** | `tasks{}`(`state` → 列) |
| 点卡片 → 会话 | **会话跳转** | `sessions[].id8` |
🔴 **关键决定:⛔ 不直读台账、⛔ 不重算** —— 复用现有看板**已经算好的** `/board.json`
(重算 = 把 `board.py` 那套逻辑抄第二份 ⇒ 同族 P0「一条规则两份实现」必漂)。
### 🔴 列用**机制真实四态**,⛔ 不硬套它那五列
- 它:待规划/待办/进行中/已完成/已失败(5 列)
- 本机制台账只有 **待执行/执行中/有阻碍/已完成**(4 态)
- ⇒ 硬套 5 列会**凭空多出机制里不存在的「待规划」**(看着像有、其实永远空)⇒ ⛔ 不做。
- ⚠️ 用户问「什么工作流状态 干啥用的」⇒ 就是这一列的含义,已当面解释。
### 🔴🔴 本轮踩的真坑:`pythonw.exe` 下 `sys.stdout is None`(终端好、计划任务死)
- **症状**:计划任务 `Result=1`,**20100 起不来**;但**前台跑同一脚本一切正常**。
- **根因**:计划任务用 `pythonw.exe`(**无控制台**)⇒ **`sys.stdout is None`**
⇒ 我写的 `sys.stdout.flush()` 抛 `AttributeError: 'NoneType' object has no attribute 'flush'`
⇒ 进程**当场崩**。而 `Result=1` 只说"失败",**看不出为什么**。
- **诊断方法(✅ 可复用)**:加一个**只写异常到文件**的 wrapper(`launch-diag.py`),
指向计划任务动作 ⇒ 异常立刻现形(⛔ 别靠猜 `Result=1`)。
- **修法**:加 `_say()` —— **有 stdout 才写、没有就静默丢**;⛔ 常驻进程不许因为没地方打印就崩。
- 🔴 **同族铁律**:**`pythonw.exe` 下 `print`/`sys.stdout` 一律不可靠**
⇒ 凡"计划任务跑的脚本"⛔ 不许裸用 `print`。
### ⚠️ 另一条弯路(别重蹈)
- 我一度以为是**中文路径**(`交付物/项目管理界面`)导致计划任务失败 ⇒ 复制到 ASCII 路径 `tmp/pm-board` **对照**
⇒ **同样 `Result=1`** ⇒ 证明**与路径无关**,真因是上面那条。
- ⇒ 教训:**路径猜测**要有对照实验才下结论(我用了一轮,但及时纠正并清理了临时副本)。
### 清理
- `tmp/pm-board/`(对照实验副本)**已删**;计划任务已**重指回正式交付目录**。
---
## 40、goalctl 两处口径分叉修复(用户 ⑤-2「vibe-product 最新会话反应新问题 看看如何解决」)
### 40.1 是什么问题
vibe-product 上一条会话(10-06 08:15~08:20)已诊断出两条缺陷并**修了数据侧**(换任务图、存值改 `pass`),
但它自己在记忆里登记了「**同族缺陷未修 / 技能侧缺陷未修**」⇒ 我这一轮把**技能侧**修了。
用户在 ⑤-2 里说的「新问题」,逐字对应那份 `目标执行状态.md` 的两条**阻断**:
| 阻断 | 内容 | 归属 |
|---|---|---|
| ① | `goal.json.execution_doc` 指向**前身目标的目录**(换目标漏改) | 工作区数据(前一会话已修) |
| ② | 本目标**从未建立过任务会话**(零排期)+ `NEXT.md` 是过期投影 | 工作区数据(已随目标完成自然消解) |
⇒ 但**真正跨工作区、属技能侧的根因有两处**,前一会话只**绕开**了、没修:
### 40.2 🔴 根因一:`goalctl.py:262` 判定层死板比字面值
- 原文:`if str(acc[k]) != "pass":` —— **只认英文 `pass`**。
- 而存值**真源就是中文**(`过|pid 8024 活…` / `已过|…` / `达(1440 与 390 两视口都有)`)。
- ⇒ 已通过的验收**全部被计入 why** ⇒ 恒判「未完成」⇒ 控制台打出
「未完成(验收 V1=过;V2=过;…)」这种**字面自相矛盾**的读数。
- 🔴 **病根不只是这一行**:`board.py::acc_is_pass()` 的 docstring 明写
「三处判据(`board.py` / `collabd.py::_acc_is_pass` / `board.html::accIsPass`)**必须同款**」,
而 `board.py:1618` 与 `collabd.py:3022` **都做了中文归一化**(`ACC_PASS_WORDS` + 剥完成副词)
—— **实际是四处,`goalctl` 被漏掉了** ⇒ 同一字段三套读法。
### 40.3 🔴 根因二:`goalctl.py:136` 配置落点写死技能目录
- 原文:`CFG = HERE / "collabd.config.json"`,`HERE` = **技能包目录**。
- 按定则「**技能就是技能、程序就是程序,谁用产生的文件放在他自己那里**」⇒
**技能目录里⛔ 不放生产配置** ⇒ 该文件**恒不存在** ⇒ `_load(CFG,{})` 恒 `{}`。
- ⇒ `taskgraph` 取默认 `INBOX/taskgraph.json` ⇒ **配置改了不生效**
(实测:vibe-product 配置里已改指 proto-board,`goals_open()` 仍报「任务图读不到」)。
- `collabd.py` 早有 `_cfg_candidates()` 三级解析(env → 工作区标准落点)⇒ **又是"一个技能两套读法"**。
### 40.4 处置
1. `goalctl` 新增 `_acc_is_pass()`:**复用 `collabd._acc_is_pass()`**(它自己再复用 `board` 真身);
拿不到 ⇒ **fail-closed 声明式兜底**+stderr 留痕,⛔ 绝不静默返回。
2. `CFG` 改为与 `collabd._cfg_candidates()` **同款三级**(env → 工作区标准落点 → 旧路径+告警)。
3. 修正 `:605` 误导文案(原「要 `名字=pass` 或 `名字=未过`」——`未过` 与任意中文值旧判定层同样恒判未过)。
4. 新增 2 条自检用例+`pitfalls.md` **P0-87**。
### 40.5 实测读数
| 项 | 修前 | 修后 |
|---|---|---|
| vibe-product `goalctl.goals_open()` | `{'open': True, 'why': [], 'bad': ['任务图读不到(taskgraph.json)']}` | **`{'open': False, 'why': [], 'bad': []}`** |
| vibe-product `goalctl` 状态台 | (目标判不出来) | **「目标状态 = 三路全过」** |
| 技能 `selftest` | PASS 97 / FAIL 0 | **PASS 99 / FAIL 0** |
| `--verify` | — | **rc=0 ✅** |
**变异对照**:改回 `!= "pass"` ⇒ PASS 97 / FAIL 1(**命中本用例**);还原 ⇒ 99/0 ✅
### 40.6 🔴 两处踩坑(都是"我喂错了,不是代码错")
1. **`selftest` 的 `imp()` 加载的是 `collabd`,不是 `goalctl`** —— 两者 `goals_open()` **同名但返回类型不同**
(collabd 返 `bool`、goalctl 返 `dict`)⇒ 直接用 ⇒ `TypeError: 'bool' object is not subscriptable`。
2. **两个模块的 `INBOX` 不是同一个目录**(`collabd` 来自配置、`goalctl` 硬编码 `WS/tmp/supervise-inbox`)
⇒ 夹具写错落点 ⇒ `bad=['任务图读不到']` ⇒ **用例恒红**。
⭐ **教训:用例红了先分"被测代码错"还是"我喂错了"** —— 三次都是后者,
若直接去"修代码",会把好的代码改坏。
### 40.7 🔴 又撞一次 CRLF(本轮第二次)
`goalctl.py` 工作副本被改成 **CRLF**(877 个 CR),HEAD 是 LF ⇒ `git diff` **整文件重写**
(877 增 / 672 删)⇒ 按字节归一化回 LF ⇒ 降到 **228 增 / 23 删**真内容。
**铁律:看到"整文件重写"先查行尾**(`b.count(b'\r')`)。前一次是 `board.html`。
### 40.8 挂账(未做,需用户拍板)
🔴 **vibe-product 的收口动作未做**:它的 `lifecycle` 仍是 `进行中`(前一会话只改了 `acceptance_state`,
**没走收口流程**)⇒ 常驻程序**按设计继续跑**(收工只在 `--set-life 已完成` 时触发)。
要收工=改 `lifecycle=已完成` ⇒ 会 **`supervise_stop` 停掉该工作区的常驻进程**。
⚠️ 这属「**影响面超出本平台/跨工作区状态变更**」⇒ **先报用户,⛔ 不擅自动手**。
⚠️ 另:vibe-product 常驻主循环跑的是**旧代码**(09-29 启动,配置改动需重启才生效)——
本次修的是**技能源**,其工作区副本要同步+重启才生效(这一步同样未做)。
## 41、项目管理界面改「跨工作区」+ 增删落地(用户:「项目管理管的是本机所有工作区的项目 不是单个工作区的」)
### 41.1 用户口径(逐字)
> `项目管理管的是本机所有工作区的项目 不是单个工作区的,可以通过项目管理工具 创建新目标(新工作区),管理目标`
外加三条 AskUserQuestion 选定:① 项目范围=**只列「装了机制」的工作区**;② 建新目标=**填表建目录+登记目标**;
③ 写权限=**允许增删,改状态交给机制**(承接上一轮 `增删查(状态不需要改 让会话机制自己处理)`)。
### 41.2 数据面(🔴 关键设计:直读各区文件,⛔ 不拉各区看板)
**真因**:并非每个区都起着 `board.py`(本机 3 个装机制的区里只有 1 个在看板跑)⇒ 拉看板会**静默漏掉没起看板的区**。
✅ 改直读:`<区>/tmp/supervise-inbox/goal.json`(项目)+ `tasks.json`(卡片)+ `collabd-state.json`(队列)
+ `<区>/.workbuddy/collab/collabd.config.json`(判「装没装机制」)。
**「装了机制」判据**:① 有 `collabd.config.json` 或 ② 有 `goal.json`(两条任一)。
🔴 跳过的**也要带出来**(`skipped` 列表)—— 同族 P0「作用域不许静默排除」,⛔ 不静默丢。
### 41.3 新增能力
| 能力 | 实现 | 实测 |
|---|---|---|
| **查** | `GET /api/projects` | 3 个项目(本机协作 / vibe-product / agent-product 空壳)+ 12 个未装机制目录 |
| **增** | `POST /api/create` → `init_workspace.py` | `ok=True rc=0`,骨架+目标登记齐全 |
| **删(轻)** | `POST /api/delete {mode:"goal"}` | `goal.json` → `goal.json.archived-<时间戳>`(可逆) |
| **删(重)** | `POST /api/delete {mode:"ws"}` | **送系统回收站**(可逆) |
### 41.4 🔴🔴 本机回收站:`gio` 不存在,改走 `SHFileOperationW`
**症状**:`mode="ws"` 原写 `gio trash` ⇒ `gio: command not found`(PortableGit 里 `gio`/`trash-put`/`trash` **一个都没有**);
改用 PowerShell `[Microsoft.VisualBasic.FileIO.FileSystem]` ⇒ **被安全策略拦**("Add-Type compiles and loads .NET code at runtime")。
**✅ 正解**:纯 `ctypes` 调 Windows Shell API `SHFileOperationW`(不编译代码、不碰 .NET、不弹 UAC)。
**三个坑**(都踩过):
1. 🔴 `pFrom` **必须手搓双 `\0` 结尾缓冲** —— `LPCWSTR` 会在**第一个 `\0` 截断** ⇒ 字段类型必须写 `c_void_p`,
用 `ctypes.create_unicode_buffer(abspath, len(abspath)+2)` 再 `cast` 塞进去。⛔ 不能直接赋 `str`。
2. 🔴🔴 **判据不是 `rc == 0`**:送回收站**成功**(目录消失、`$I` 元数据可解出原路径),返回值却**恒为 `rc=2`**
(`ERROR_FILE_NOT_FOUND`,Shell 内部探测残留的 `GetLastError`)⇒ 拿 `rc` 当判据会**假报错**
(用户看到"失败"但东西已进回收站)。✅ 真判据只有两条:**① 原路径不存在了 ② `fAnyOperationsAborted` 为假**。
3. ⚠️ 回收站 `$I` 元数据:`v2` 格式、路径是 **UTF-16LE**、起点 **offset 24**(⛔ 不是 26;开头 4 字节是长度前缀)。
验证可恢复性时按此解析。
**实测取证**:`_pmtest-{verify,trash,wsdel,wsdel2,http}` 全部送进 `E:/$RECYCLE.BIN/S-1-5-21-…-500/`,
共 **29+ 条 `$I` 元数据**,逐条解出的原路径与送入路径一致 ⇒ **可恢复**。
### 41.5 另一个已修缺陷:子进程中文乱码
`init_workspace.py` 输出 **UTF-8**(开头 `reconfigure("utf-8")`),而 `subprocess.run` 不给 `encoding`
时按**本机默认编码(中文 Windows=GBK)**解码 ⇒ 中文全变乱码。✅ 修:`encoding="utf-8"` + `PYTHONIOENCODING=utf-8`。
### 41.6 起服务:只许触发计划任务
`(python project-board.py --serve 20100 &)` **活不过工具调用边界**(被 SIGTERM)。
✅ `Start-ScheduledTask -TaskName 'dsh-project-board-keepalive'`(动作走 `pythonw.exe` + `project-board-launch.py`,
⛔ 不直起业务脚本 —— 同族 P0-73/74)。重启后 PID `64312` → `10408`,`/healthz` HTTP 200。
### 41.7 挂账
- 🔴 **vibe-product 是否收工** —— 仍待用户拍板(见 §40.8,未动)。
- 🔴 技能仓历史分叉未决(§36,仍待拍板)。
## 42、项目管理界面:统一视图 + 提示收进「!」 + 数据时效(用户逐字驱动)
### 42.1 用户口径(三条逐字)
1. `没安装机制的工作区不显示 黄字描述都放到 !号的图标中,不显示在工作台`
2. `这里分类不要按照工作区分了,记住 项目管理是统一管理`
3. (我反问刷新机制后)`页面应该有自己的刷新机制,保证数据的有效性,不然看着刷新了实际没有刷新`
### 42.2 落点
| 项 | 做法 |
|---|---|
| **统一视图** | ⛔ 删掉「按工作区分块」⇒ **一张四列看板**汇总本机所有目标的卡片;归属改由**卡片上的「目标」标签**体现(`.badge.goal`) |
| **提示收进「!」** | 工作台上去掉所有黄字横幅(原 `notice` 容器**整体删除**);无内容时图标淡显不可点,有内容点开弹层、点外/Esc 关 |
| **未装机制不显示** | UI 彻底不列(含原「扫到但没装机制 N 个目录」那行)⚠️ 后端仍留 `skipped`(只给 CLI/取证) |
| **数据时效** | 服务快照带 `epoch`;读数「数据 N 秒前」**只用服务 epoch 算**;>15 s 转黄(`.is-stale`);每秒重画(⛔ 不重取数) |
| **轮询自适应** | 有变化 ⇒ 3 s;连续 3 次没变 ⇒ 15 s;一变立刻回收。⛔ 不做"页面醒了才刷"这类花活 |
| **操作反馈** | 一次性 toast(新建/删除/失败),⛔ 不常驻 |
### 42.3 🔴 「看着刷新了实际没刷新」的根因与修法(用户第 3 条的直接答复)
- **病根**:原来 `stamp` 显示的是**服务返回的 `ts` 字符串**,而页面每 5 秒无条件 `setInterval(load)` ⇒ 每次拉取都会把它改成"刚刚"
⇒ **数据其实没变,但读数一直在跳** ⇒ 用户看到的"新"只是**页面在轮询**,⛔ 证明不了数据是新的。
- **✅ 正解(照现有看板 `board.html::paintAge` 的口径)**:① 读数**只用服务给的 `epoch`** 算
(⛔ 不用本地 `Date.now()` 顶上);② 超阈值**转警示色**;③ 读数**每秒重画但不重取数** ⇒
数据没动时读数会**如实往上爬**,一眼分清"页面在转"与"数据在动"。
- ⚠️ 浏览器侧验收**踩了缓存坑**:第一次探针读出 `groups=3 / cols=8 / goal_badges=[]`(=旧版),
差点判成"改没生效"⇒ 必须 `Network.setCacheDisabled` + `Page.reload(ignoreCache=True)` 才读到真页面
(同族 P0:「浏览器侧验收假阴性」)。
### 42.4 实测(浏览器侧·强刷新后)
- 4 个列标题(⛔ 不再是 8 个);12 张卡片全在一张板上;标签 `本机协作 ×1 / vibe-product ×11`
- 「!」1 条内容、默认收起、点击展开、再点关闭;时效读数正常跳动
- 死代码清理:随分区一起废掉的 `projgroup/pghead/pgname/pglink/pgmeta/pchip/lane*` 共 15 行,已全数删除(残留检查全 0)
- 提交 `d97fb0f`(工作区)
## 43、vibe-product「最新会话反应的问题」核查(用户第 2 问)
### 43.1 问题出处
正式载体=`执行会话/目标-vibe-product-5d27fe/目标执行状态.md`(第 1 棒检查会话,2026-10-05 18:0x 写)。
它报**两条阻断 ⇒ 判不出来**:① `execution_doc` 指向前身目标目录;② 本目标**从未建立过任务会话**(零排期)。
### 43.2 核查结论:两条都已被后续动作消解,**但留下了三条新问题**
| 原阻断 | 现状 | 判 |
|---|---|---|
| ① `execution_doc` 指旧目标 | 已改指 `执行会话/目标-vibe-product-5d27fe/`(**该目录现存在**) | ✅ 已解 |
| ② 零排期 | 台账 11 件全 done、任务图 N1~N4 全 done、验收 V1~V4 全 pass | ✅ 已解 |
| ③ 判据与存值口径 | `goalctl.py` 已同步(与技能源 **md5 相同** `c4eb6c02…`) | ✅ 已解(本轮所修) |
🔴 **但留下三条**(见 §43.3)。
### 43.3 🔴 三条新问题(都没人报,是本轮核查挖出来的)
1. **`NEXT.md` 是过期投影,还在喊「V1 pending」**
`tmp/supervise-inbox/NEXT.md`(**10-05 18:06**)内容是本目标**四条 V1~V4**、
而 `goal.json` 现已是**另一份目标**(「规划产品原型项目管理工具」),且它自己的那条 V1 早过。
⇒ **文件在,读数全错**。⚠️ 真因:清 `NEXT.md` 的 `paused_round()` **只在 `--once` 分支跑**,
常驻主循环走**另一条路** ⇒ 常驻模式下这份投影**永不被清**。
2. **常驻在空转,且规模已可观**
`pid 432` 起于 `08:50:47`,`round` 累计至 **54**;日志 **10,102 行 / 1,412,248 字节**。
去重后**只有三类内容**,全是"没事发生":
- `闸④:跳过不会再触发的一次性排期(⛔ 否则它会永远卡住闸)|<12 条旧排期名>`
- `目标检查:同名排期已在册 ⇒ 跳过([检查]-[目标检查]-vibe-product-第2棒)`
- `跨区自愈:ai1net-dsh-server=目标已完成 且队列无未完成件 ⇒ 不拉`
⇒ 每分钟固定刷约 10 行,**纯开销、零产出**。
3. **工作区副本跑的是 09-29 的旧代码**
`.workbuddy/collab/collabd.py` 未同步技能源(`goalctl.py` 已同步,但 `collabd.py` 未核)。
### 43.4 与用户第 1 问的交叉
🔴 **第 2 问的根因正好被第 1 问的界面暴露出来**:统一视图把 12 张卡片摊在一张板上,
一眼能看出 **11 张是 `vibe-product` 的、且全 `done`** —— 即"这个目标已干完、常驻还在空转"。
⇒ 我的处置建议:**收工该目标**(`lifecycle=已完成` ⇒ 会 `supervise_stop` 停掉该区常驻)。
⚠️ 但这属**跨工作区状态变更** ⇒ 仍按 §40.8「先报用户、⛔ 不擅自动手」。
## 44、A 方案执行:收工 vibe-product(用户拍板)+ 界面三处返修
### 44.1 A 方案 —— 收工 vibe-product(**已执行并取证**)
用户拍板「A. 只收工 vibe-product」。正解入口=**`collabd.py --set-life 已完成`**
(⛔ 不是手改 `goal.json`:`goal_life_set()` 在写盘后会**自动调 `supervise_stop()`**,
手改只写字段、**常驻照跑**)。
cd E:/ProgramData/AIProject/vibe-product
python .workbuddy/collab/collabd.py --set-life 已完成 \
--by "pm-board-main-20261006" --check-why "三路全过、队列全 done ⇒ 用户拍板 A 方案:收工"
回显:`✅ 目标状态:进行中 → 已完成 |常驻程序:已优雅退出(pid 432,因:目标已完成…)`
🔴 **三条取证**(⛔ 不看回显就报成功):
| 观测量 | 收工前 | 收工后 |
|---|---|---|
| `tasklist /FI "PID eq 432"` | 进程在 | **没有运行的任务匹配指定标准** ⇒ 真没了 |
| `_collabd.log` 35 s 增量 | 32 s 涨 **1357 B**(每分钟约 2.5 KB 空转) | **0 字节** |
| `goal.json.lifecycle` | 进行中 | 已完成(`lifecycle_at/by/why` 留痕齐) |
⚠️ 备份在 `tmp/supervise-inbox/goal.json.bak-setlife-20261006-0926`(11160 B)⇒ 可回滚。
🔴 **连带结论($43.3 三条新问题):① 已消解**(收工即停写投影);**③ 不成立**(`collabd.py`
副本与技能源 md5 都是 `955bd92009d7cd0cccc53d867f591b49` ⇒ 已同步,**上轮记的"未核"要划掉**)。
②「`NEXT.md` 过期投影」的**共性缺陷仍在**(`paused_round()` 只在 `--once` 分支跑)
—— 停了这个区只是治标,⛔ 未修机制。
### 44.2 界面返修三处(用户当轮四条反馈)
**① 「卡片挤到一起去了」—— 真因=嵌套 grid(P0 级,我自己上一轮引入的)**
`renderAll()` 写 `cols.innerHTML = '
' + …`,而容器 `#cols` **本身就带
`class="cols"`** ⇒ 内层 div 成了外层 grid 的一个 grid item,先被压到 `min-width:auto`
(=内容宽 **399 px**),再在 399 px 里自己切 4 列 ⇒ **每列 89 px、卡片 67 px**。
实测:col 89px / card 67px / 页高 3192px
修后:col ~400px / card 381px / 页高 1415px
✅ 判据:**`#cols` 是容器时,内层⛔ 不能再套 `.cols`**。同族坑:光看 CSS 全对
(`.cols{grid-template-columns:repeat(4,minmax(0,1fr))}` 逐字正确),**读样式表查不出错**
⇒ 必须量 `getBoundingClientRect()`。⚠️ 我上一轮"统一视图"改造时引入,**用户肉眼先发现的**。
**② 「卡片上下之间的距离太远」** grid 默认 `align-items:stretch` ⇒ 12 张卡全在「已完成」、
另三列空着,**四列被拉成等高** ⇒ 整页虚胖。改 `align-items:start`。
连带:卡片 `gap` 9→6、`.cards` padding 10→8、`.col` `min-height:160px`→0、`main` 下 padding 60→40。
**③ 工作区 / 目标 两个维度串了(用户逐字纠正:「下拉框是选工作区的 跟目标没关系」)**
我上一版写「全部(**N 个目标**)」,而 N 取的是 `projects.length` = **工作区条数**
⇒ 把两个维度混成一个数(用户看到"3 个目标"才追问"本机不是只有一个目标吗")。
事实(`/api/projects` 实测):
| # | dir | name(改前) | has_goal | 卡片 |
|---|---|---|---|---|
| 0 | `ai1net-dsh-server` | 本机协作 | true | 1 |
| 1 | `vibe-product` | vibe-product | true | 11 |
| 2 | `agent-product` | (未命名 · agent-product) | **false** | 0 |
⚠️ 我一度把 #2 判成"空壳"、建议藏起来 —— **用户否了**:「加载机制就可以显示」
⇒ 它**装了机制只是没登记目标**(目录里**没有 `goal.json`**,但有 `collabd-state.json`
+ `_ensure.stamp`)。用户说它的目标是「加载机制」。
⇒ **口径:装了机制的工作区一律列出**,⛔ 不许因"没目标"就藏(同族「作用域不许静默排除」)。
改后:① 标签「项目」→「**工作区**」;② 选项文字=**目录名(+目标名)**;
③ 「全部(**3 个工作区**)」;④ 概要条分开报「工作区 3 个 / 其中已登记目标 **2** 个」;
⑤ `!` 弹层措辞改「装了机制但还没登记目标」。
**④ 「显示的是目录名,应该是真正名称」** `project-board.py:209` 原 `name = short or title`
⇒ 登记过 `short` 的区一律显示短名。改 **`title or short`**(都没有 ⇒ 「未登记目标 ·
」,
⛔ 不再印「(未命名 · …)」,用户原话「未命名 看不懂个是什么」)。
🔴 **我怀疑过是收工操作的副作用,查完是虚惊**:`goal.json` 的 `title`/`short`
**收工前后逐字一致**(与备份 diff)⇒ 不是新问题,是老口径。
⇒ 教训:**报"疑似副作用"前先 diff 备份**,⛔ 别让怀疑变成向用户上报的事实。
### 44.3 卡片信息重排 + 本工作区高亮
- **工作区名移到卡片末行**(用户:「工作区名称要不要 放到卡片下面」)⇒ 采纳。
`.wsrow`(上边框分隔)+ `.wsname`(等宽字体)+ `.wsself` 角标「本工作区」。
⛔ 不再混在 badge 堆里(那样会被忽略)。
- **本工作区卡片换色**(用户:「本工作区的卡片是不得 换个颜色」)⇒ `.card.is-self`
蓝底 + 蓝框 + 标题提亮。🔴 判定**来自服务端 `self` 字段**,⛔ 前端不硬编码目录名:
启动器注入 `DSH_PM_BOARD_SELF_WS`=**启动器文件位置往上两级**。
⚠️ ⛔ 不能用 `Path.cwd()` —— 实测计划任务的工作目录是 `交付物/项目管理界面`,
**不是工作区根**,那样算出来的"本工作区"是错的。
- **修时间戳印成 `1791957640.7257342`**:台账 `t_start`/`t_end` 有两种形态
(ISO 串 / **纯数字 Unix 秒**),原 `fmtWhen` 只认前者、不匹配就**原样返回**。
补:`1e9 < n < 4e9` ⇒ 走 `new Date(n*1000)` 转本地时间。
### 44.4 浏览器侧验收(沿用的两条硬规矩)
1. **必须强刷新**:`new_tab` 后直接读 DOM 会读到**旧缓存**(本轮又差点误判)。
✅ `cdp("Network.setCacheDisabled", cacheDisabled=True)` + `cdp("Page.navigate", …)`
+ `wait_for_load()` + `sleep(3)`。
2. **量尺寸⛔ 不靠读 CSS**:本轮"CSS 全对但渲染全错"就是靠 `getBoundingClientRect()` 抓到的。
3. ⚠️ harness 预导入里**没有 `sleep`** ⇒ 脚本头部自己 `import time; def sleep(s): time.sleep(s)`。
4. ⚠️ 探针选择器要跟着结构改:去掉内层 `.cols` 后 `#cols > .cols > .col` **恒为 0 条**
(不是布局坏了)⇒ 改 `#cols > .col`。
## §45 dsh- 技能整合完整性排查 + 决策判据常驻注入落地(2026-10-06 上午)
### 45.1 决策判据常驻注入(用户明令:「决策方法必须加载到每次对话中」)
**用户拍板**:归属=并入会话技能 `session-mechanism`;注入内容=**判据式精简版**。
**根因(三条实测)**:
1. 判据住在 `dsh-decision/references/`,而 `dsh-decision` **不在** `session-mechanism` 安装面;
2. `install.py` 声明表**逐字写着** `⛔ decision_bridge 不在此表内` ⇒ 跑「配置环境」带不上;
3. 🔴🔴 **`session-mechanism/SKILL.md:395` 既定口径「判据实体就在本档正文里,⛔ 不依赖任何其他技能」
与本包实际打架** —— 10-04 那次**只搬了 `01-功能优先协作协议`(问不问)**,
**决策方法论(U27/A6)从未搬入**(证据:本包正文搜 `U27`/`A6` **零命中**)⇒ **口径落空**。
**落地(6 件,一次做完)**:
- 新建 `scripts/hooks/decision-rules-hook.py`(挂 `SessionStart`,注入 1459 字符判据式精简版)
- `install.py`:**声明表 +1 条** + **`OWN_BASENAMES` +1**(⛔ 不加后者 ⇒ `--uninstall` 认不出、旧接线删不掉)
- 逐字搬入 `references/04-决策方法论.md`(33197 B / md5 `ab06e860…`)+ `dsh-decision-method/` 三份素材库
- 钩子指针**从外部包改为包内** ⇒ 真做到「⛔ 不依赖任何其他技能」
- 修掉搬入件里 **1 处源件既有断链**(`素材库-U-提报用户.md` → `素材库-U-用户决策.md`),并加搬运登记块
- `--dry-run` → `--apply` → `--verify` **全绿**(`SessionStart` 2→3 条,`decision_bridge` 4 条未被触碰)
⚠️ **踩到的头号坑**:`RULES` 常量里**中文引号写成了半角 `"`** ⇒ 字符串提前闭合 ⇒
`invalid character '/' (U+FF0F)` **整个钩子加载失败**。⇒ **改这文件后必须跑 `ast.parse`**(已写进文件头)。
### 45.2 dsh- 六个技能「说要整合、实际没整合」排查 → **结论:零遗漏**
| 技能 | 声称 | 实测 |
|---|---|---|
| dsh-decision | 2 原名合并 | ✅ 2 档 + 素材库子目录,清单表齐全 |
| dsh-diagnose | 3 原名合并 | ✅ 3 档,**每档都有 provenance 头 + 原行段声明**(整合范本) |
| dsh-knowledge | 2 原名合并 | ✅ 2 档 + 子目录 |
| dsh-local-env | 2 原名合并 | ✅ 2 档 + 子目录 |
| dsh-workflow | 2 原名合并 | ✅ 2 档 + 2 个子目录 |
| dsh-opensource-release | 无合并声明 | ✅ 5 档齐全 |
**11 个退役名全都有落点**;`references/` 引用 **零真断链**。
⚠️ 疑似断链 5 个(`dsh-users-platform`/`dsh-web-platform`/`dsh-laijing-github`/`dsh-hosting`/`dsh-univer-office`)
**是仓库名/插件名,⛔ 不是技能名**。
⚠️ `editing-guide.md`/`examples.md` 是 `last_change` 里叙述**外部技能** `shuorenhua` 的历史,⛔ 不是本包引用。
### 45.3 我本轮的三次误判(元教训)
1. **"dsh-diagnose 三档无 provenance"** → 撤销:档**都有** provenance 头。
真因=我的检测脚本**判据太窄**(只认文件名/目录名,不认档头「本档覆盖」声明)。
2. **"dsh-decision/workflow 清单表没登记子目录档"** → 撤销:**都登记了**(第 69/75/76 行)。
真因=脚本正则只认 `references/xxx.md`,**认不出目录行** `references/xxx/`。
3. **上一轮"界面四列全空"** → 撤销:**界面是好的**(已完成列 12 张卡)。
真因=`/api/projects` 的 `cols` **只是标签**,卡片在 `projects[].cards`;
我探错了端点(`/api/board` 404 ⇒ 空 JSON ⇒ 误判)。
🔴 **判据(写进 P0-93)**:**"未发现" ≠ "不存在" —— 报缺失之前先证明你的检测器能看见它。**
三次误判全是同一个病:**判据写窄了 ⇒ 假红**(同 `dsh-local-env` 记的「判据写窄=假红」)。
### 45.4 清理
- **5 个 `.bak-*`** → 归档到 `<工作区>/归档/技能bak清理-20261006/`(带技能名前缀防撞名,**可整目录还原**)。
⚠️ 本机 `Add-Type` 与 COM `Shell.Application` **都被沙箱拦** ⇒ 回收站路走不通 ⇒ 用项目既有的「移到归档目录」。
删前安全检查:3 份正本**都在、都比 bak 新、`.py` 语法 OK**。
- **1 个 `__pycache__`**(`dsh-local-env/.../resident-rules.cpython-313.pyc`)→ 直接删(纯缓存)。
### 45.5 项目管理界面:关系纠正(用户第 1、2 问)
**三层**:① **工作区**(目录,装了机制,**3 个**)② **目标**(`goal.json`,**2 个**)③ **任务卡**(`tasks.json`,**12 张**)。
- 用户第 1 问「工作区筛选里为什么加目标描述」⇒ ✅ **我对**:下拉该只列**工作区名**(目录名)。
- 用户第 2 问「已完成里显示的不是目标」⇒ ✅ **用户对**:那是**任务卡**(`t`/`3b-v1`/`ACCEP-FIX-01`…),
且台账 `title` 字段是 `None` ⇒ **回落成卡 id**,所以「看着不像目标」。
- ⏳ **待用户拍板**:一个工作区多个目标 ⇒ 界面读「当前目标」/ 读 `goals/` 历史 / 改多目标模型(三选一)。
## §46 项目管理界面:四列=目标(三改其正)+ 一个区多目标
> 🔴🔴🔴 **本节最该记的一条**:我连续**三次**把界面语义做错,每次都是**自己拍脑袋定模型、
> 没先查数据**。用户三次纠正(「已完成里显示的到底是不是目标」→「里面显示的都是目标
> 目标就是项目」→「但是你现在 已完成里面显示的 就不是目标信息,连一个目标的名称都没有」)。
> ⇒ 判据:**改"数据语义"之前,先把真源文件打开看一眼**(`goal.json` / `goals/*.json`)。
### 46.1 错在哪(三版)
| 版 | 我以为 | 实际 | 用户看到 |
|---|---|---|---|
| 1 | 四列装**任务台账**(`tasks.json`) | 四列该装**目标** | `3b-v1`/`dark-tokens` 一堆任务 id |
| 2 | 一个工作区**只有一个目标**(`goal.json`) | 一个区**可有多个**目标 | 「已完成」只剩 1 张,历史目标不见了 |
| 3 ✅ | 全部目标=`goal.json` + `goals/*.json` | — | 4 个目标名,四列正确 |
### 46.2 数据模型(实测坐实 · ⛔ 别再猜)
- `tmp/supervise-inbox/goal.json` = **当前目标**(恒 1 个)
- `tmp/supervise-inbox/goals/*.json` = **历史目标归档**(0..N 个;换目标时旧的那份挪进来)
- **全部目标 = 当前 + 历史**;`vibe-product` 实测 3 个(当前 1 已完成 + 历史 2)
- `lifecycle` 真身 5 值:`等待/进行中/阻碍/已完成/已暂停`(`collabd.py::GOAL_LIFE_*`)
⇒ 映射四列:等待→待执行|进行中→执行中|阻碍→有阻碍|已完成→已完成;**已暂停无列**
- ⚠️ `lifecycle` **可能带括号后缀**(`已完成(机器可判部分)`,手改进去的)⇒ 必须**剥后缀**再判
### 46.3 改了什么
- `project-board.py`:新增 `goal_life_of()`(**与 `collabd.py` 同源**:剥括号 + 别名表)、
`_load_all_goals()`(当前+历史,历史按 `declared_at` 倒序);
`COLS` 列键改 `waiting/active/blocked/done`;`cards` = 目标卡(一条=一个目标);
`tasks` 另存任务细账;`queue` 统计**去掉 `by` 兜底**(`by` 现在数目标,兜底会静默串数)。
- `project-board.html`:`_cardHtml` 主标题=**目标名**(⛔ 不回落到 id,那正是"看着不像目标")、
加 `.badge.cur`「当前」标记;概要区改「工作区 N 个 / 目标 N 个 / 其中执行中 / 已完成 /
任务台账 x/y」(**目标维度 与 任务维度 分开报**);下拉**只印工作区名**(⛔ 去掉了目标名拼接)。
### 46.4 布局事故(另记)
- 我以「空列白占屏」为由把四列改 `flex` +空列收窄 ⇒ 用户「现在是改乱后的样子」⇒ **已恢复**
`grid-template-columns:repeat(4,minmax(0,1fr))`。**判据:四态列固定维度,必须并排等宽;
"某状态是空的"不是布局问题,⛔ 不许因空列改列宽。**
## §47 详情页判据改卡片 + 修「张冠李戴」缺陷
### 47.1 用户要求
「优化点击目标打开的详情页(右侧滑动的那个),里面的判据用卡片显示,现在列表字段都挤成竖列了」
### 47.2 病根(不只是样式)
- `.flow{display:flex}` 把「判据键」「判据值」摆**同一行左右两侧** ⇒ 抽屉只 560px 宽
+ 值长(实测一条 100+ 字)⇒ **压成窄竖条**,几乎不可读。
- ⚠️ 附带帮凶:代码里 `a.v.slice(0,120)` **截断判据值** ⇒ 用户以为"判据就这么短"。
### 47.3 🔴🔴 **顺带查出的真缺陷:判据张冠李戴**
- 一个工作区**多个目标各有各的判据**(实测 `vibe-product`:当前目标 4 条 /
历史目标 V1-V4 4 条 / 历史目标 N1-N3 3 条)。
- 但服务端 `p.acc` **只装当前目标的**,前端详情页读 `p.acc`
⇒ **点历史目标的卡,显示的是当前目标的判据**。
- ✅ 修法:每条目标卡自带 `acc`(该目标自己的判据);前端 `var acc = t.acc || p.acc || {}`。
- 🔴 同型缺陷:主档字段原读 `p.declared_at`/`p.life_at`/`p.topics`(=当前目标的值)
⇒ 一并改读 `t.xxx`。**判据:详情页一切字段取"卡片自己那份",⛔ 不用工作区那层。**
### 47.4 改了什么
- CSS:新增 `.pcards/.pcard/.phead/.pk/.pv/.pst`(卡片式判据,一卡一条,
垂直布局=键在上值在下,左色条 green/red 区分过/未过)+ `.fcard`(任务细账卡片)。
- JS `openDetail()`:判据渲染改卡片;**去掉 `slice(0,120)`**;`acc` 改取 `t.acc`;
主档字段改 `t.xxx`;抽屉标题改**目标名**(⛔ 原显示 id `vibe-product@cur`)。
### 47.5 验证(实测尺寸,⛔ 不看布尔值)
- 判据卡宽 **527px**(铺满抽屉 560px 内),值框宽 **501px**
- 值 55 字 ⇒ 值框 501×40(2 行正常换行);值 34 字 ⇒ 501×20 ⇒ **不再是竖列**
## §48 UserPromptSubmit 超时:不是脚本慢,是「7 次冷启动 + 串行」
### 48.1 用户报障
`UserPromptSubmit operation blocked by hook` × 4 条,全部 `timed out after 10000ms`:
`reply-style-guard` / `stop-dialog-guard` / `skill-load-guard` / `session-log-guard`。
### 48.2 🔴🔴 根因(实测,⛔ 不是"脚本慢")
| 跑法 | 耗时 |
|---|---|
| 任一条单跑 | 0.23–0.36 s |
| 4 条各自单跑(累计,模拟串行) | **1.247 s** |
| **7 条并发**(多进程) | 0.84 s |
- `UserPromptSubmit` 上**串行挂了 7 条 hook**,每条=**一个独立 Python 进程** ⇒ 宿主每轮
**冷启 7 次解释器**(Windows 上 import `json`/`re`/`datetime` 都要重来)。
- 前 3 条预算 15+30+20 ⇒ 后面 4 条 `timeout=10` **被排队挤破**。
- ⚠️ 别被报错里的路径带偏:有一条写着 `E:/ProgramData.workbuddy/...`(**少一个点**)——
实测该目录**不存在**,只是历史 trace 里的残影,**不是当前接线**。
### 48.3 ✅ 治本:4 条守卫合并成一个进程
- 新增 `scripts/hooks/prompt-guards.py`:一次读 stdin ⇒ 本进程内 `runpy` 依次跑 4 个 guard
(临时接管 stdout 收 JSON)⇒ 合并输出。
- 实测 **1.247 s → 0.357 s(3.5×)**;注入内容**逐字一致**(654 字符 `additionalContext` 完整保留)。
- ⛔ **不许合并**:`supervise-ensure-hook`(常驻确保)、`wb-result-hook`(结果投递)、
`decision_bridge`(别处接线)—— 有副作用/不同语义,合并会改行为。
- 治标(也做了):4 条 timeout 10→30。
### 48.4 🔴 踩到的实现坑(合并入口)
`_Tee.buffer = self`(`TextIOBase` 子类)⇒ `sys.stdout.buffer.write(bytes)` **撞上
`TextIOBase.write`** ⇒ bytes 被 `str()` 成 `b'{"..."}'` ⇒ **JSON 解析必失败**
(现象:报"输出了非 JSON(6228 字节)")。
✅ 正解:`buffer` 用**独立的 `io.BytesIO`**;文本/字节两路**各收各的**。
### 48.5 顺带修的两处
- `reply-style-guard.py` **此前是手工接线**,⛔ 一直不在 `install.py::OWN_BASENAMES`
⇒ `--uninstall` 认不出。已补登记。
- 包内残留 `references/pitfalls.md.bak-p93-*`(我上轮留的)⇒ 移归档,
否则 selftest「包体卫生」FAIL。
### 48.6 验收
`install.py --verify` **全绿**(接线 9 条空载荷自检全 ok)|selftest **PASS 99 / FAIL 0**。
---
## §49 vibe 会话「会话技能没同步最新版」排查(用户第 10 问)
### 49.1 用户报障
`还有 vibe 会话一直说 本会话 会话技能没同步最新版 检查下`
### 49.2 🔴 报障出处(已定位,⛔ 非"猜")
= **`session-rules-check.py` 的 ⑧/B 组 `mem_ptr` 项**(「会话规则机制体检」),
经 `state.py` §5b 每轮随状态快照跑 ⇒ **stdout 摘要每轮打印 ⇒ 用户看到的就是它**。
实跑复现(`--ws .../vibe-product`):
```
🔴 [会话规则] 规则机制有硬缺口(见 fail 项)(fail 3 / warn 3 / ok 8)
✗ [B] 工作区记忆里的技能指针悬空(每轮注入 ⇒ 一直把人引错) —— product-planning
```
落盘标记 `/.workbuddy/collab/session-rules.json`:`verdict=fail`、`mem_ptr=product-planning`。
### 49.3 🔴🔴 真因:**技能根解析到「全局」库,而被测技能在「工作区」库**
判据(`session-rules-check.py:73` 与 `state.py:339` **同源同病**):
```python
SKILLS = os.environ.get("DSH_SKILLS_ROOT") or os.path.join(CFG, "skills") # CFG=CODEBUDDY_CONFIG_DIR
```
- 本机 `DSH_SKILLS_ROOT` **未设** ⇒ 解析到 **`E:/ProgramData/.workbuddy/skills`(全局)**。
- 而 `product-planning` **只在** `vibe-product/.workbuddy/skills/`(工作区库)**存在**,
全局库**没有**它(实测:`ls E:/ProgramData/.workbuddy/skills/product-planning` → No such file)。
- ⇒ `EXISTING_SKILLS`(=全局库目录名集合)里没有 `product-planning` ⇒ **被判"悬空"**。
- ⚠️ **这是假红**:`MEMORY.md` 里那句「`product-planning/`」指的是**工作区内的技能目录**,
它**真实存在**;判据却只查了全局库 ⇒ **判据的作用域错了,不是指针真断了**。
### 49.4 🔴 顺带查实的两条真问题(都不是这条报障,但确实存在)
① **工作区技能副本整体落后全局(13 个文件内容不同 + 全局独有 8 个)**
逐文件 md5 对比(`tmp/probes/_sm_diff.py`):
| 文件 | 全局 mtime | 副本 mtime |
|---|---|---|
| `SKILL.md` | 10-05 17:36 | 10-05 16:20 |
| `scripts/collabd.py` | **10-06 04:43** | 10-05 16:20 |
| `scripts/goalctl.py` | **10-06 08:44** | 10-05 15:33 |
| `scripts/selftest.py` | **10-06 08:42** | 10-05 12:08 |
| `scripts/board.py` | **10-06 05:08** | 10-05 15:25 |
| `assets/board.html` | **10-06 05:06** | 10-05 12:09 |
| `references/pitfalls.md` | **10-06 10:32** | 10-05 16:20 |
| `install.py` | 10-06 10:28 | **10-04 07:52** |
| ⛔ 全局独有 | `scripts/hooks/prompt-guards.py`(本轮新建)、`scripts/hooks/decision-rules-hook.py`、`references/04-决策方法论.md` 等 8 个 | — |
⇒ **副本缺的正是本轮与近两轮的改动**(`prompt-guards.py` 合并入口、`decision-rules-hook`、
决策方法论文档)。⚠️ 与 §43.3 第 3 条「副本跑的是旧代码」**同一根因,且已从"09-29"恶化到"10-05"**。
② **`snap_sync` 恒 warn**:`vibe-product/CODEBUDDY.md` **不存在** ⇒ 判据第 338 行直接 warn
(「找不到权威规则文件」)。⚠️ 本工作区确实没有 `CODEBUDDY.md` ⇒ 这条**不是故障,是判据前提不成立**。
### 49.5 ⛔ 我差点误报的一条(**已用探针证否,记下来防复发**)
终端 `head`/`cut` 读副本 `SKILL.md` 时显示 `浼氳瘽鏈哄埗`(=「会话机制」的 mojibake),
**看起来像文件被 GBK 写坏**。⇒ 写探针判**原始字节**(`tmp/probes/_enc_probe.py`):
`utf-8 解码 ✅ 通过`、`无乱码特征`、`CR=0 LF=938` ⇒ **文件本身完全正常**,
乱码只是 **Git Bash 终端显示层**的问题。🔴 **教训:判编码必须读字节,⛔ 不能信终端回显。**
### 49.6 结论与处置建议(⛔ 本条只做诊断,未动手改)
| 项 | 判 |
|---|---|
| 用户看到的 `mem_ptr` fail | 🔴 **判据作用域错误(假红)** ⇒ 正解是判据改为「全局 ∪ 工作区」两个库都查 |
| 副本落后全局 13 文件 | ✅ **真问题** ⇒ 按 `副本使用说明.md §六 四步同步纪律`(先 diff、只覆盖真差异、⛔ `roots.env` 绝不覆盖、跑一次自测) |
| `snap_sync` warn | ⚠️ 判据前提不成立(本区无 `CODEBUDDY.md`)⇒ 应改判「本区无权威文件 ⇒ 跳过(not ok)」 |
---
## §50 「统一都用工作区版本」——核对结论:**这本来就是这个包的既定设计**
### 50.1 用户提问
`要不要统一都用 工作区版本,修改全局版本后 同步到工作区?`
### 50.2 🔴 结论:这套策略**早就定了**,而且写在 `deploy_code.py` 的文件头里(2026-10-03 用户定案)
用户原话(引自 `deploy_code.py` 头注逐字):
> 「**跨工作区使用会话协作技能,除了看板共用,其余都是独立的,包括程序和相关文件**」
⇒ 现行架构成型如下(⛔ 不是我提的新方案,是**既有设计未被执行**):
- **技能目录 `E:/ProgramData/.workbuddy/skills/session-mechanism/` = 源(唯一真身)**
- **每个工作区 `.workbuddy/collab/` = 自己的副本**(各区独立跑、互不牵连)
- **分发工具 = `scripts/deploy_code.py --ws <工作区>`**(清单 `DEFAULT_FILES` 7 个文件)
- **看板 `board.html` 是唯一共用件**(⛔ 不在分发清单内)
### 50.3 🔴 佐证:两个常驻**确实都在跑各自工作区的副本**(进程级取证,⛔ 非看文件)
`Get-CimInstance Win32_Process` 实测 argv:
- `PID 64392` → `ai1net-dsh-server/.workbuddy/collab/supervise-launch.py`
- `PID 21888` → `vibe-product/.workbuddy/collab/collabd.py --supervise`
- ⇒ 机制已是「各区独立」,**问题不在架构,在"改完源忘了跑 `deploy_code.py`"**。
### 50.4 🔴 实测漂移(`ai1net` 副本 vs 全局源,逐文件 md5)
**清单内 7 个(`deploy_code.py::DEFAULT_FILES`)**:
- ❌ `collabd.py`(副本 10-05 20:03|源 **10-06 04:43**)—— 常驻本体,**漂得最要命**
- ❌ `goalctl.py`(副本 10-05 17:01|源 **10-06 08:44**)
- ✅ `collabctl.py` / `guard.py` / `session-rules-check.py` / `init_workspace.py` / `supervise-launch.py`
**清单外但也漂了(首版漏网,同一教训 P0-57 同族)**:
- ❌ `board.py`(副本 10-05 15:25|源 10-06 05:08)
- ❌ `selftest.py`(副本 10-05 17:14|源 10-06 08:42)
- ❌ `stop-collab.py`(副本 10-01 18:51|源 10-02 21:30)
- ❌ `wake-session.py`(副本 09-30 12:54|源 10-02 21:26)
### 50.5 与 §49 的关系(⛔ 别把两件事混了)
- §49 的 `mem_ptr` 假红 = **判据作用域错**(`vibe` 侧、`product-planning`)—— 是**体检误报**。
- §50 的副本漂移 = **分发没跑**(`ai1net` 侧,`collabd.py`/`goalctl.py`)—— 是**代码真的旧**。
- 两者**同族**(都是「改了一处、别处没跟上」),但**修法完全不同**:前者改判据,后者跑分发。
### 50.6 ⛔ 本轮仍未动手(只取证)
---
## §51 「重启后自决策方法生效了吗」→ 顺势全量体检技能机制,修 3 个真缺陷
### 51.1 用户两问
① 「已经重启,现在自决策方法生效了吗」 ② 「发现问题 检查所有技能相关机制 是否正常」
### 51.2 ✅ 原问答复:**已生效**(进程级+内容级双证)
- `decision-rules-hook.py`(SessionStart)实测 `inject ... len=1459`,14:14:53 就在跑;
注入正文=「【决策判据 · 常驻(违反即事故)】… 🔴 目标不打折,路径取最小代价(U27 + A6)…」。
- `prompt-guards.py`(UserPromptSubmit 合并入口)实测 rc=0、产出 654 字符、**4 guard 合计 0.058 s**
(stderr 自报 `reply=0.020s stop=0.024s skill=0.011s session=0.003s`)。
- 结论:**你上一轮做的 hook 合并 + 决策判据注入,重启后都在跑。**
### 51.3 🔴 但全量体检挖出 **3 个真缺陷**(都已修 + 变异对照)
**① `stop-dialog-guard.py` 每轮崩溃 —— 整条钩子实际是死的(最严重)**
- 症状:`stop-dialog-guard.log` **86 条 `EXCEPTION|ValueError: not enough values to unpack (expected 3, got 2)`**,首条 05:09:26,**每轮复现**。
- 真因:`session_budget()` 的**返回元组长度不一致** —— 两条早退路径 `return None, None`(**2 值**),
而末尾 `return last_in, n_calls, prev_in`(**3 值**),调用方第 611 行按 **3 值**解包。
- 触发条件:transcript **> 64 MiB** 或文件不在。**本会话 transcript 实测 192.8 MB** ⇒ 每轮必命中。
- 🔴 **为什么一直没人发现**:本钩子是 **fail-open**(异常仍 `sys.exit(0)`)
⇒ 宿主零报错、`install.py --verify` 只判 `rc=0` ⇒ **判它 "ok"(假绿)**。
而实际死掉的是**整条**:水位与收口 / 接续机制起点 / 预算告警 / 门禁自检 / 路径自检。
- 修:两处早退改 `return None, None, None` + docstring 补齐三元说明。
- 验:同 transcript 复跑 ⇒ **EXCEPTION 停在 86 不再增长**,且**首次**写出完整日志行
`invoked(user-prompt)|mode=inject|…`(以前在到达之前就崩了)。
**② `session-rules-check.py::KEY_HOOKS` 未随合并更新 ⇒ 每轮「关键钩子不在册」假红**
- 真因:上一轮把 4 条 UserPromptSubmit 守卫合并成 `prompt-guards.py`,而本判据仍按**旧文件名**找
⇒ 惩罚的正是「按要求做过的合并」。
- 修:`KEY_HOOKS` 第 2 元由**单名**改**一组名**(命中任一即算在册)+ 匹配/报错两处同步。
- 验:`hook_reg` fail → **ok**(ai1net fail 3→2)。
**③ `snap_sync` 用 mtime 当内容判据 ⇒ 连续 4 天假红**
- 真因:原判据 `getmtime(auth) - getmtime(snap) > 0.01 天` ⇒ 权威 `CODEBUDDY.md` 只要**被 touch 过**就报
「快照比权威旧」,而**快照内容一个字都没差**(实测 md5 逐字节相同 `f54e48d4…`、`diff` 0 行)。
- 🔴🔴 **我中途踩的坑(必须记)**:第一次修成「调 `resident-rules.py --check`」,
**变异对照当场证否** —— 把快照**截断到 400 字节**,它照样报 `✅ 关键规则齐备`(rc=0)
⇒ **判据变恒绿,比原来的假红更坏**。真因:`--check` 校验的是**目标 CODEBUDDY.md 里规则齐不齐**,
**根本不含"与快照比对"**。
✅ 正解:**导入抽取器本体**(`importlib` 加载 `resident-rules.py`,把它的 `SNAP` 常量临时改指临时文件,
调它自己的 `snapshot()` 取"应有内容")⇒ 与磁盘真快照比对 ⇒ ⛔ 不抄第二份抽取逻辑、⛔ 不碰真快照。
- 验(四段):正常→ok|**变异(截断)→fail(判据不是恒绿)**|还原→ok|无 `CODEBUDDY.md` 的区→warn「本项不适用」。
**④ 顺带:`mem_ptr` 只查全局技能库 ⇒ 工作区自带技能被判悬空(vibe 假红,即 §49)**
- 真因:`EXISTING_SKILLS` 只 `listdir(SKILLS)`(全局根),而 `product-planning` **只在**工作区根的技能库。
- 修:`_skill_names()` = **全局根 ∪ `/.workbuddy/skills`**;判据问"**本机取不取得到**",⛔ 不问"全局库有没有"。
- 验:vibe `mem_ptr` fail → **ok**;合成区(含假技能名)仍能报 fail(**非恒绿**)。
### 51.4 🔴 元教训(写进 pitfall P0-95)
- **「换了个看起来更对的调用」≠「判据变强了」** —— 第 ③ 条我差点交付一个恒绿判据。
⇒ **凡改判据,必须重跑变异对照**(真去破坏一次,看它会不会报)。
- **fail-open + 只看 rc=0 的体检 = 假绿温床**:`install.py --verify` 9 条全 "ok",其中一条其实是死的。
⇒ 判「钩子活着」要**读它自己的日志尾部**(有没有 EXCEPTION / 有没有写出完整 `invoked` 行),⛔ 不看 rc。
### 51.5 分发与回归
- `deploy_code.py --ws <区> --only session-rules-check.py` ⇒ ai1net / vibe **md5 均一致**(`b2b46899`)。
- `install.py --verify` **全绿**(PASS 99 / FAIL 0)。
- ⚠️ `session-rules-check.py` 由 `state.py` **每轮现调** ⇒ 免重启;⛔ **`collabd.py` 改了才需重启常驻**。
### 51.6 改动文件
- `~/.workbuddy/skills/session-mechanism/scripts/hooks/stop-dialog-guard.py`
- `~/.workbuddy/skills/session-mechanism/scripts/session-rules-check.py`(4 处)
- `~/.workbuddy/skills/dsh-local-env/.../常驻规则-快照.md`(重生成本无变化,仅 mtime)
- 备份:`tmp/bak-resident-rules-20261006/`
---
## §52 「提问机制又失效了?」「需要决策的格式没了吗?」→ 核实:**机制全在跑,失的是我的执行**
### 52.1 用户三问(同一件事)
① 「是不是 提问机制又失效了 为什么提问不给 选项」② 「需要决策的 格式排版都没了吗」③ 「这些规则应该固化到 每次会话中上下文中」
### 52.2 🔴 核实结论:规则**本来就每轮都在上下文里**(不是失效,是我没照做)
三处独立证据,全部实测:
- **每轮注入在跑**:`reply-style-guard.log` 在你最近三条消息上**逐条命中** ——
`15:49:10` / `15:49:38` / `15:49:59`,每条都是 `core=562 字符` + `HIT … 1647 字节`。
⇒ **你每说一句话,排版规则就被注入一次**。
- **规则文本本来就在里面**:`REPLY-CORE` 块(`CODEBUDDY.md:313-322`)**第七条**逐字写着
「**待拍板项**:放回复**最后一节**,逐条编号;每条写清「问题 + 说明(影响谁/断多久/花多少钱/有无不可逆)
+ 各候选的优点与缺点 + 倾向」」—— 我要的格式,规则里**一条不缺**。
- **无 `CODEBUDDY.md` 的工作区也不漏**:`reply-style-guard._core()` 是**三级回退**
(① `/CODEBUDDY.md` → ② **包内** `references/03-回复排版-核心块.md` → ③ 外部技能那份),
实测 `vibe-product` / `agent-product` **都没有 `CODEBUDDY.md`**,走第②级照样有注入。
- **SessionStart 另有一路**:`decision_bridge.py` 注入 378 字符《提问规范》(含"选项:每个候选各占一段、
必须写清优点与缺点"),14:14:53 实测在跑;本会话 transcript 里「提问规范」出现 **65 次**。
⇒ **结论:机制没坏、没退役、没静默。你上一轮看不到选项,是我把自己写的"待处理清单"当成了汇报,
没按规则做成待拍板项。** 这是执行问题,不是机制问题。
### 52.3 🔴 但我确实发现一个真缺口 ——「变相征询」没被规则明文堵住
- 我上轮用的是「**先只报不动**」+「**等你发话**」—— 它**不是征询句**(逃过 `stop-dialog-guard` 的收尾判据),
也**不是**标准的"待拍板项"(没写候选优缺点)⇒ **形式上两头都不沾,实质上就是要用户拍板**。
- 旧规则只堵了「征询句收尾」与「待拍板项要写全」两处,**中间这条缝没人管**。
- ✅ 处置(单源改造,未抄第二份):
① 权威源 `agent-operating-rules/references/回复排版-核心块.md` 加一条
「🔴🔴 **「变相征询」同样禁止**:凡是要用户拿主意的事(含"先只报不动/等你发话/我倾向X你看呢"这类
**不带选项的待定清单**)一律按待拍板项写(问题+说明+**各候选优缺点**+倾向);反过来自决的写成陈述句」;
② 同步内联副本 `session-mechanism/references/03-回复排版-核心块.md`;
③ 用既有注入器 `agent-operating-rules/scripts/apply-reply-rules.py --apply --ws <区>` 重生成 `CODEBUDDY.md` 的块。
### 52.4 验收(逐项实测)
- `apply-reply-rules.py --check`:改前**报不一致**(`a791f6be… vs a04b29fa…`)⇒ 改后**一致 ✅**。
- 核心块 **562 → 835 字符**;每轮注入实测 **927 字符**,且 `含「变相征询」= True`、`含「待拍板项」= True`。
- 备份 `CODEBUDDY.md.bak-replycore-20261006-161328` 已挪出工作区根 → `tmp/bak-replycore-20261006/`。
### 52.5 顺带查实(不是缺陷,记下来免得下次再查)
- `ai1net-decision-laya/runtime/services.paused`(10-06 04:24)= **只暂停推理类服务**("决策只落库"那一档),
**不影响提问规范门**(门是纯本地字符串判定,不调模型、不起进程)。
- `decision_questions` 表最新一行停在 **10-06 01:46**,且最近几行 `gate_verdict=weak/blocked` ——
⚠️ 该表只在**识别到提问**时落行;上轮那套「不带选项的清单」两路都没命中,所以**没落行**(与 52.3 同因)。
- `apply-reply-rules.py` **不在** session-mechanism 包内、而在 `agent-operating-rules` 里
(包内那份只是"内联副本",会话技能的钩子优先读 `/CODEBUDDY.md` ⇒ **改口径要两处同步 + 重跑注入器**)。
### 52.6 改动文件
- `~/.workbuddy/skills/agent-operating-rules/references/回复排版-核心块.md`(权威源 · +1 条)
- `~/.workbuddy/skills/session-mechanism/references/03-回复排版-核心块.md`(内联副本 · 同步)
- `E:/ProgramData/AIProject/ai1net-dsh-server/CODEBUDDY.md`(REPLY-CORE 块由注入器重生成)
---
## §53 用户授权「同步到仓库」⇒ 工作区已推、**技能仓主动停在推送前**(红线)
### 53.1 已做
- **工作区仓**(`git@work.alotbuy.com:maogeigei/dsh_shenxian_workspace.git`):
提交 `4106327`(`CODEBUDDY.md` + `.workbuddy/memory/2026-10-06.md`)⇒ **推送成功**(`77b9f73..4106327`)。
- **技能仓**(`git@work.alotbuy.com:maogeigei/workbuddy_skills.git`):
提交 `c4de1ae`(5 文件 / +314 −28,只 add 自己改的,未碰他人 11 处删除)⇒ **push 被拒(non-fast-forward)**。
### 53.2 🔴🔴 为什么停手(取证在案,⛔ 不许擅自强推)
- `fetch` 后对比:**本地领先 29 个提交,远端只有 1 个** ——
`19101ac`(maogeigei,2026-10-05 14:13)**「init: workbuddy_skills 重建,仅收录 session-mechanism」**。
- 远端树里**技能目录 = 1 个**(只有 `session-mechanism`);**本地 = 25 个**。
- 🔴 **凭据实况**:`ls-tree -r` 命中 `.neodata_token` ——
**远端 `origin/master` = 0(干净)**,**本地 `master` = 1(当前版本仍跟踪)**,
工作区磁盘 `E:/ProgramData/.workbuddy/skills/.neodata_token` **仍在**。
- ⇒ 结论:远端那次重建**本身就是要甩掉凭据**(与 `MEMORY-仓库推送与授权.md` 记的
「自 `43b83b0` 含明文令牌;彻底清须 `push --force` + 服务端吊销」完全对上)。
⇒ **本地 master 一强推,就把凭据重新推上远端** ⇒ 这是**安全事件**,⛔ 不是普通冲突。
⇒ 按 `CODEBUDDY.md §1`(边界外必问,⑦ 红线门禁 ⑥ 影响面超出本平台)**主动停手并上抛**。
### 53.3 附带在途风险(本轮才发现,需一并处置)
- **本地技能仓的「当前版本」仍在跟踪凭据文件**(不只是历史 blob)⇒ 任何一次 `git add -A` + push 都会外泄。
⚠️ 与记忆里「已 `rm --cached` + 补规则」的旧结论**不一致** ⇒ 旧结论已过期(疑被后续提交又带回)。
- 我的 `c4de1ae` 本身**不含凭据**(提交前逐行扫过 `token|password|secret|api_key|PRIVATE KEY`,只命中 docstring 里的 `token` 一词)。
### 53.4 未做(等拍板)
- ⛔ 未 push 技能仓(任何形态:普通 / force)。
- ⛔ 未改写历史(`filter-repo` / `filter-branch`)。
- ⛔ 未动远端那 1 个提交,也未动本地 25 技能。