Files
dsh_ai1net_server/.workbuddy/memory/2026-10-06.md
T

513 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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「先报用户、⛔ 不擅自动手」。