Files
dsh_ai1net_server/.workbuddy/memory/2026-10-14.md
T
admin c1707b7c97 记忆:补记「技能根绝对路径清零」
用户令:「SKILL.md:189 判据源写 E:/ProgramData/.workbuddy/skills/
严格禁止技能中写这里绝对路径(除非跨根目录因引用),跨环境全部报错」

判据:技能根(本包自己 + 同根技能)一律不写绝对路径;
跨根引用(工作区根 / 文档库根 / 本机安装位 / 宿主数据目录)才落到具体位置。

技能仓提交 2f65609(10 文件 · 19 增 18 删),已推送。
另 15 处在 dsh-knowledge / dsh-workflow,两包被 .gitignore 全挡且无自己的 .git ⇒ 改动不入仓。

本轮最大教训:Grep 工具(ripgrep)跳过被 ignore 的文件,第一遍扫描结论是错的;
改用 grep -r 才照出漏网的 15 处。核实"清干净没有"一律用 grep -r。
2026-10-14 21:11:33 +08:00

646 lines
50 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-14 工作日志
## 主会话 · 排查「执行/检查会话批量操作不能归档、不能删除」(07:2x~07:4x)
**用户提问**:为什么会话机制创建的执行会话和检查会话,批量操作时不能归档也无法删除。
**取证路径**(全部只读,未改任何代码与数据):
1. 只读打开宿主库 `E:\ProgramData\.workbuddy\workbuddy.db`,查 `sessions` 表结构与取值分布。
2. 读出 asar 头部索引(13910 文件),自建 `tmp/asar-extract.js` / `tmp/asar-grep.js` 做定点抽取与全量检索。
3. 抽取并核实 `main/conversations.js`、`main/handlers.js`、`main/server.js`、
`renderer/assets/adapters-*.js`、`renderer/assets/DataManagementPanel-*.js`、
`renderer/assets/ui-docs-viewer-*.js`。
**结论(产品层设计,非本项目机制缺陷)**:
1. 产品**没有会话的批量归档/批量删除接口** —— `archiveSessions`/`deleteSessions`/`batchArchive` 全量检索 0 命中。
2. 归档可用性判据(`ui-docs-viewer`):
`_n = e => !((e.status??'').toLowerCase()==='archived' || e.projectId || I4n(e, 空间))`
⇒ `archived` / `projectId` 非空 / `I4n` 命中,三者任一即不可归档。
`I4n(e,t) = e.transport==='cloud' ? false : e.spaceType==='assistant' || M4n(e.cwd,t)`。
3. 归档只改 `status`,`deleted_at` 保持 `NULL`;而保留策略自动清理对已归档项是 `archivedSkipped += 1; continue`
⇒ 归档不会让会话消失,只移出默认列表。
4. 「删除」在本地是**软删**(`SET deleted_at = ?`,`transport='local'`),行仍留在 `sessions` 表
⇒ 从库里看「像没删掉」。`deleted_at = -1` 是另一套语义(云端索引 `WHERE deleted_at = -1` 表未删)。
5. `DataManagementPanel` 的批量交互操作的是「归档列表项」,按 `kind` 分流;
`readonly` 项(`origin` 为 `project`/`cloud`)**结构上不渲染操作按钮**。
**现场读数**:`sessions` 412 行,`deleted_at IS NOT NULL` 319 行;
`[执行]`/`[检查]` 会话全部 `is_background_automation=1` + `transport='local'`。
**产出**:`tmp/会话批量归档删除-排查-20261014.md`(完整证据链与可复现取证方法)。
**未做(等拍板)**:机制层加「批量归档/清理已完结执行·检查会话」的动作 —— 属直接改宿主库,
须守活库改动铁律(停 app 或 `backup()` API,⛔ 禁文件级 `cp`)。
**本机坑(再次命中)**:⛔ 别把 MSYS 路径(`/e/…`)交给 Windows 原生 node,会落到 `E:\e\…`;一律传 `E:/…`。
---
## 主会话 · 排查「mcn-aigc-ontology 为什么自己创建了一个主会话」(07:34~07:40)
**用户提问**:mcn-aigc-ontology 为什么这个工作区运行会话项目机制,自己创建了一个主会话。
**现场读数**(全部只读):
1. 该区已装机制:`.workbuddy/collab/collabd.config.json` 在位,`supervise.switch` = `on`。
2. `sessions` 表里该 `cwd` **只有 1 条会话**:`9e069caf`「加载会话技能」,`status=working`,
`is_background_automation=None`(**不是**机制建的),`created_at` 07:12:14 —— **这条就是当前用户在用的主会话本身**。
3. 该区 `automations` 只有 **2 条**:
- `473c13a3`「`[主]-super-i卡片采集-主会话`」`recurring` `FREQ=DAILY;BYHOUR=09;BYMINUTE=0`
`cwds=["E:/ProgramData/AIProject/mcn-aigc-ontology"]`,`created_at` **07:31:11**,`last_run_at=None`
(**下次触发 09:00,从没跑过**)。prompt 逐字:「你是本工作区的主会话……要推进就直接建 `[执行]` 排期拉起任务会话」。
- `5e16f0de`「`[执行]-super-i卡片采集-全量抓取与落盘`」`once`,`scheduled_at=2026-10-14T07:35`,`created_at` **07:31:19**。
4. 常驻:`logs/supervise-heartbeat.json` → `pid=38624`、`started_h=07:30:37`、`round=25`、
`argv0` 指向本区 `.workbuddy/collab/collabd.py`(**argv0 合规**)。
5. 🔴 `logs/_collabd.log` 每 10 s 一条 **`all_sessions_idle 读库失败 no such table: sessions`**
—— **典型「启动器丢了 `CODEBUDDY_CONFIG_DIR`」故障**(读 0 字节空库 ⇒ 检查程序静默失效)。
**结论:机制没有"自己创建主会话"。** 真实发生了四件事,被误读成"机制自创主会话":
> 🔴🔴 **2026-10-14 07:53 订正(用户点破)**:本行结论**只对了一半,且说偏了**。
> 用户原话:「**那就更不对了,技能中没有任何创建主会话的机制,除非是创建接续会话**」。
> 「机制**运行中**没有偷偷建主会话」这半句对;但**技能文档里确实存在"排期创建主会话"这条路**(写成了合法场景),
> 那才是真问题。⇒ **别照本行往下读,改看本文件末节《订正「技能里存在"创建主会话"这条路"》》。**
> (本行保留仅为留痕 —— 「过程 ≠ 成品」同理:错结论⛔ 不删,加状态块指向订正处。)
1. **07:12** 用户自己在该区开了一条会话(标题「加载会话技能」)= 本区**真主会话**。
2. **07:30** 该区常驻被拉起(`pid 38624`),随即把这条 07:12 的主会话**登记进 goal.json**
(日志逐字:`登记主会话 9e069caf(创建目标时顺手登记)`)。
3. **07:31** 该会话(或同轮动作)**新建了一条 recurring 排期** `[主]-super-i卡片采集-主会话`,
`cwds` 指本区、每天 09:00 触发 —— **这条排期到点会真的开出一条新的主会话类型会话**。
4. 同一分钟还建了一条 `[执行]` once 排期(07:35 触发)。
⇒ 「自己创建了一个主会话」的**可见现象**=排期台账里那条 `[主]-…-主会话`;
它**尚未运行**(`last_run_at=None`),**不是已经多出来一个窗口**。
**风险(实测,未处置)**:
- 该 recurring 排期**每天 09:00 会开一条新会话**;若长此以往 ⇒ 每天多一条"主会话"⇒ 与用户实际在用的 `9e069caf` **并存**,
变成"一个工作区两个主会话"。技能口径:**主会话由用户手动开,⛔ 不该由排期生产**。
- `_collabd.log` 的 `no such table: sessions` ⇒ 该区**检查程序(建检查会话那条腿)静默失效**。
与该排期是否清理**是两件独立的事**。
**本机机制层观察**:排期 `scheduled_at` 给了毫秒级时间戳;`rrule` 为空串(once)。
---
## 主会话 · 修复 mcn-aigc-ontology 两项(07:35~07:45)
用户令:「两个问题都按照建议进行修复」⇒ 按倾向 A 执行。
**修复一:撤掉每日主会话排期**
- `[主]-super-i卡片采集-主会话`(id `473c13a3`)已置 **`PAUSED`**,`next_run_at` 清空。
- 用 `automation_update` 改状态(⛔ 未用 rm/sqlite,符合机制铁律);**零删除、可逆**(改回 ACTIVE 即恢复)。
- 那条 `[执行]-super-i卡片采集-全量抓取与落盘`(`5e16f0de`)保持 ACTIVE,07:35:09 已正常触发建出执行会话 `c6226e12`。
**修复二:常驻读库故障(`no such table: sessions`)**
- 真因(证据链):`supervise-launch.py` 的默认值在 **10-11 被改成 `~/.workbuddy`**,
而本机 `C:\Users\Administrator\.workbuddy\workbuddy.db` 是 **0 字节、零张表**(实测);
真库在 `E:\ProgramData\.workbuddy\workbuddy.db`(124 MB)。ai1net 区没炸是因为它 config 里 `host_db` 兜住了。
- 修法(双保险,与技能 P0-73 口径一致):
① `supervise-launch.py`:默认值改回**真库绝对路径** `E:\ProgramData\.workbuddy`(`setdefault` 语义保留,宿主 env 优先);
② `collabd.config.json`:补 `"host_db": "E:/ProgramData/.workbuddy/workbuddy.db"`(**本该有的单区兜底**)。
- 改前备份:`supervise-launch.py.bak-configdir-20261014-073619`、`collabd.config.json.bak-hostdb-20261014-073619`。
- 重启:计划任务 `collabd-keepalive-mcn-aigc-ontology`(`pythonw.exe`,合规)⇒ 旧 `pid 38624` → 新 `pid 28628`。
**验收(三条对齐,非恒绿)**:
1. 启动器解析出的库 = `E:/ProgramData/.workbuddy/workbuddy.db`,`sessions` 413 行读通。
2. 重启后日志:`all_sessions_idle:还有 1 条 working(c6226e12)⇒ 不算全结束` —— **正常读到库**,
本轮 `no such table` **0 条**(重启前是每 20s 一条、连续 25+ 轮)。
3. **变异对照**:旧形态(`~/.workbuddy` + 无 `host_db`)⇒ 复现 `no such table: sessions`;
新形态 ⇒ 读通 413 行。⇒ 判据有效,不是恒绿。
**遗留(技能/其他区层面,本轮未动)**:
- 🔴 **技能源 `E:\ProgramData\.workbuddy\skills\session-mechanism\scripts\supervise-launch.py` 第 30 行仍是
`os.environ.setdefault("CODEBUDDY_CONFIG_DIR", os.path.expanduser("~/.workbuddy"))`** ⇒
**凡是照技能源部署的新区,都会命中同一个空库故障**。本区已单区修好,技能源未动 = 待拍板。
- 各区部署副本形态不一(实测):
`ai1net-dsh-server`/`contentm_agent`/`mcn-aigc-ontology`(已修)= 写死 `E:\ProgramData\.workbuddy` + config 有 `host_db`;
`aigc-idea-impression` = 写死绝对路径但 **config 无 `host_db`**(靠启动器那条腿活着,实测日志正常);
`vibe-product` = 未部署启动器。
- 本机 `~/.workbuddy/workbuddy.db` 那个 0 字节空文件仍在(无害,但会持续误导"以为 `~` 是活动环境")。
**终态复核**:写法定为与 `contentm_agent` 逐行一致(`setdefault` + 绝对路径),语法 OK、重启后 `pid 38428`、读库失败 0 条。
---
## 主会话 · 技能源相对化 + 四区统一部署(07:45~07:52)
用户令:「按照建议处理,技能中 **文件路径 必须用相对路径**」⇒ 该条**推翻了上一轮"给各区写死绝对路径"的做法**。
**动因**:上一轮为救 mcn 区把技能源里的 `~/.workbuddy` 换成写死绝对路径 —— 这本身是**把「一台机器的环境」焊进技能**,
与既有的 `collabctl.py::_resolve_airoot()` 口径(⛔ 不在代码里写死盘符/目录)冲突。用户第四条指令正是纠这个。
**改法:`HERE` 相对基准 + 三级外置取值 + ⛔ 禁回落 `~`**
```
HERE = os.path.dirname(os.path.abspath(__file__)) # 全部路径以 HERE 为基准(相对)
CB = <HERE 推导出的 collabd.py>;CFG = <HERE 推导出的 collabd.config.json>
_resolve_config_dir() 三级:
① os.environ["CODEBUDDY_CONFIG_DIR"] —— 宿主 env 已给 ⇒ 直接用,⛔ 不动
② 本区 collabd.config.json 的 host_db —— 取其父目录
③ 包内 env/roots.env 的 CODEBUDDY_CONFIG_DIR —— 向上最多 7 层找 env/roots.env
三级全空 ⇒ 置空 + stderr 告警,⛔ 绝不回落 ~/.workbuddy(那是 0 字节空库)
```
- 三级取值均**外置**(宿主 env/本区配置/包内 env),代码内**零绝对路径**。
- 保留 `setdefault` 语义的反面:**宿主 env 优先级最高**(命中①时原样不动)。
- docstring 里示例路径改写为正斜杠(`E:/ProgramData/.workbuddy`)—— 顺带解掉 `\U` unicodeescape 报错。
- 新增 `import json`。
**验证(探针,非纸面)**:造临时区跑探针 ⇒
① 宿主 env 命中(`D:/fake/host/env`,原样采用);
② 无宿主 env 但有 `host_db` ⇒ 解析出 `E:\ProgramData\.workbuddy`;
③ 三者皆无 ⇒ `CODEBUDDY_CONFIG_DIR` 为 `None` 并打出告警(**fail-safe 方向正确**:宁可报错也不静默读空库)。
**分发与终态**:
- `deploy_code.py --ws <区>` 分发四区,自动备份旧副本 ⇒ **四区 `supervise-launch.py` md5 统一 `bfa03b2f`**。
- `aigc-idea-impression` 补 `"host_db": "E:/ProgramData/.workbuddy/workbuddy.db"`(JSON 校验通过)——
这是四级兜底里它原本缺的一环。
- 技能包 `env/roots.env` 第 9 行已含 `CODEBUDDY_CONFIG_DIR=E:/ProgramData/.workbuddy` ⇒ 第③级在位,无需补。
**四区常驻重启终验**:
```
ai1net-dsh-server 旧 20680 → 37280 07:50:41 ✅ 读库正常
mcn-aigc-ontology 旧 38428 → 4728 07:50:46 ✅ 读库正常
aigc-idea-impression 旧 29364 → 20304 07:50:52 ✅ 读库正常
contentm_agent 旧 27092 → 29400 07:50:58 ✅ 读库正常
日志实证:all_sessions_idle:还有 1 条 working(f920d73e)⇒ 不算全结束 / no such table 共 0 条
```
**🔴 必须记住的一条口径**:技能源改动**只影响将来部署的新区**;已在跑的四区是靠**部署副本 + 重启**生效的。
⇒ 以后改技能源,动作永远是「改源 → `deploy_code.py` 分发 → 重启常驻 → 验心跳 `started_h` 晚于部署时间」,三步缺一即未生效。
**遗留**:`~/.workbuddy/workbuddy.db`(0 字节)仍在,无害但会持续误导;`vibe-product` 未部署启动器。
---
## 沉淀(跨区通用,供后续同类问题直接引用)
- 🔴 **读写宿主库的脚本,路径一律「外置 + 相对基准」**:代码里只认 `CODEBUDDY_CONFIG_DIR`/本区 `host_db`/包内 `env/roots.env`,
⛔ 不写死盘符、⛔ 不猜 `~`。判据:把区**整个搬到另一台机器**仍能跑通 ⇒ 合格。
- 🔴 **绝对路径修复是「治好了但焊死了」**:同一症状下次换机器/换盘符必复发 ⇒ 只能作**应急**,须在同轮内补上相对/外置改造。
- 🔴 **`~/.workbuddy` 在本机是空壳**(0 字节库),凡代码里出现 `expanduser("~/.workbuddy")` 一律视为**高危默认值**。
---
## 主会话 · 订正「技能里存在"创建主会话"这条路」(07:53~08:00)
**用户订正原话**:「**那就更不对了,技能中没有任何创建主会话的机制,除非是创建接续会话**」。
⇒ 我上轮把那条排期说成"多出来的主会话(只是还没跑)",**说岔了** —— 用户的点不在"它跑没跑",
而在**技能里根本就不该有"排期创建主会话"这条路**。核实结论:**用户判断正确,技能里确实有这么一条(错的)路**。
**病灶(四处,同源)**:`supervise-persistence.md` 早期把「**定时任务创建主会话**」与「用户手动创建主会话」
并列成**两种合法场景**,`SKILL.md` §⑥ 顺着抄了一遍 ⇒ 读到的人(或 AI)会理解成"这条路是允许的",于是照做。
**事故链条(mcn-aigc-ontology,全部实测)**:
1. 07:12 用户手动开 `9e069caf`「加载会话技能」= 该区**真主会话**(`is_background_automation=None`)。
2. 07:30 `init_workspace.py` 装机制(`goal.json` 的 `lifecycle_why` 逐字「init_workspace.py 新建」),
常驻拉起并顺手登记主会话(日志逐字「登记主会话 9e069caf(创建目标时顺手登记)」)。
3. **07:31:11 建出 `recurring` 排期 `[主]-super-i卡片采集-主会话`**(`FREQ=DAILY;BYHOUR=09;BYMINUTE=0`),
**prompt 逐字**:「你是本工作区(…)的主会话,只负责目标与方向,不亲自干活。……职责:判断与派活
(要推进就直接建 `[执行]` 排期拉起任务会话)」
⇒ **这是"主会话"的口径,被套到了一条排期身上** —— 排期开出来的会话恒是任务会话那一档。
4. ⇒ 每天 09:00 会开出一条**顶着主会话名头的任务会话**;长期跑 ⇒ 台账里"主会话"越积越多,用户那边**没多窗口**。
**权威口径(技能里本来就有,只是被上述那处盖住了)**:`SKILL.md`《三类会话》类别表逐字
——「**主会话 | 用户手动开 | 管目标与方向、派活 | 用户关窗**」;
`architecture.md §1` 同款;排期只服务**派活线**(`[执行]-…`)与**检查线**(`[检查]-…`)。
「接续会话」是主会话撞阈值时**由它自己建出来的形态**(角色继承),⛔ 不是第三条建主会话的路。
**修复(三件,已全部落地并验收)**:
**① 改文档(4 处)**:
- `references/supervise-persistence.md §〇`:表格从"场景 A/场景 B 两列"改成**只有一列**(「用户手动打开主会话」),
并新增《为什么⛔ 不能"用排期拉起主会话"》小节 —— 含事故回放 + 明确写「本档早期表格是病灶,2026-10-14 已改掉」。
- 同档 §八 对照表:**删掉「B · 定时任务创建主会话」那一行**,只留有价值的读数(`once` 过期即哑)。
- `SKILL.md §⑥`:那句「用户手动创建主会话 / 定时任务创建主会话」改成
「**形态只有一种:主会话由用户手动开** —— ⛔ 排期拉不出主会话」。
- `references/architecture.md §1`:新增 🔴🔴 段《主会话怎么来:只能由用户手动开》,含判据指针。
**② 加判据(第 ⑬ 条,`session-rules-check.py`)**:
- 扫本区排期,命中 `name` 以 `[主]` 开头或含"主会话" ⇒ `fail`(还在 ACTIVE)/`warn`(已非 ACTIVE 的留痕)/否则 `ok`。
- ⚠️ **扫描面 recurring 与 once 都含**(当年 ⑧ 只扫 recurring 才看不见机制建的 once 排期,同类坑)。
- **变异对照(证明非恒绿)**:喂 mcn 区真数据三种输入 —— 原样 PAUSED ⇒ `warn`;伪造成 ACTIVE ⇒ `fail`;
无此类排期的区 ⇒ `ok`。**三档全判得出**。夹具 `tmp/probe-main-sched-judge.py`。
- `SKILL.md §体检` 清单从「查三类、十二项」改成「**十三项**」;变异对照记录补上 ⑬ 那行。
**③ 分发与验收**:
- `deploy_code.py` 分发四区,`session-rules-check.py` md5 统一 **`4b53c88d`**(技能源同值)。
- mcn 区用部署副本真跑 ⇒ 第 ⑬ 条按预期报 `warn`(那排期现为 PAUSED)。
- **顺带抓出另一条真隐患**:那条排期 `model_id=deepseek-v4.1-flash` + `model_is_thinking=0`
⇒ **每次触发必被服务端拒**(判据 ⑧ 报 `fail`)。⚠️ 该排期现已 PAUSED ⇒ 不触发,但**若哪天改回 ACTIVE 必须同时改模型**。
**沉淀(本机口径)**:
- 🔴 **主会话只能由用户手动开**。排期开出来的会话**恒是任务会话那一档**(`is_background_automation=1`);
名字里带 `[主]`/"主会话"**不改变它的档位**。判据=排期名,见第 ⑬ 条。
- 🔴 **技能文档里"并列两种合法场景"的写法极危险** —— 它会把一条**本该唯一的路**读成"二选一"。
⇒ 写"形态"类内容一律**单列**;要留旧形态只作**事故回放/留痕**,并明确写"⛔ 别照做"。
---
## 主会话 · 三项拍板执行(09:46~09:55)
用户令(逐字):「**1、A / 2、确认调整无误 就更新 / 3、不需要就删除,避免创建无效文件**」。
**① 排期保持 PAUSED(候选 A)**
- mcn 区 `[主]-super-i卡片采集-主会话` **保持 PAUSED 不动**(`next_run_at=None` ⇒ 不触发;零风险、可逆)。
- 终态复核:该区 4 条排期 —— `[主]-…` PAUSED|`[执行]-…第2棒` ACTIVE(`next_run_at` 已排)|`[检查]-第1棒` ACTIVE|
`[执行]-…全量抓取与落盘`(首棒)已跑完。链路正常。
**② 核实无误后更新(推送技能总仓)**
- **入库前必查三件**(全部通过):白名单核对 `git ls-tree -d` = **7 个技能目录**、与 `.gitignore` 放行条逐条对上;
凭据扫描(`password/token/secret/api_key/PRIVATE/x-access-token/ghp_/sk-`)**零命中**;两个 py 语法编译通过。
- **错口径清零复核**:`SKILL.md`/`architecture.md` 里「定时任务创建主会话」「场景 B」**零命中**;
`supervise-persistence.md` 剩 4 处**全是事故回放/留痕**(明写「⛔ 别照做」)。
- **编号冲突复核**:体检清单 ⑬ 唯一;`pitfalls.md` 里那套 ⑦~⑬ 是 `t_srsm_busy` 夹具的独立编号,不冲突。
- **提交**:`053a6a6`「session-mechanism:主会话只能由用户开(撤掉"排期建主会话"那条路)」,5 files / +180 / -26。
- **推送**:`[email protected]:admin/workbuddy_skills.git` ⇒ `20eacef..053a6a6 master -> master`(成功)。
- **终验**:本地 HEAD = 远端 `origin/master` = `053a6a67231fe39bc6fcbcfe983d8ab5b6438196`;`git status` 干净。
**③ 删除 0 字节空壳库(避免误导)**
- 清单:`C:\Users\Administrator\.workbuddy\workbuddy.db` —— **0 字节**、2026-10-03 00:06 建、零张表、**无 WAL/SHM 伴生**
(该目录其余 `MEMORY.md`/`device-id`/`logs`/`observe`/`pending-telemetry` **一律不动**)。
- 做法:**送回收站**(`SHFileOperationW` + `FOF_ALLOWUNDO`,⛔ 不直删)。
- ⚠️ **删前加了一道自检**:脚本先查大小,**非 0 字节就拒绝执行**(防误删真库)。
- 结果:`rc=2`(`SHFileOperation` 的"部分成功"语义),原位已不存在。
- 影响面复核:真库 `E:\ProgramData\.workbuddy\workbuddy.db` 在位(`sessions` 416 行/`automations` 341 行);
四区常驻读库正常 —— `no such table` 最后一次出现分别是 **10-05 12:14**(ai1net)与 **今天 07:37**(mcn),
**均为重启前历史行**,aigc/contentm 为 0 条。本轮删除**未造成任何新故障**。
**临时文件清理**(用户令「避免创建无效文件」)
- 送回收站:`who-created-main-sched.py`/`main-sched-prompt.py`/`trash-empty-db.py`/`cleanup-20261014.py`(清理脚本自身也一并送走)。
- **保留 1 个**:`tmp/probe-main-sched-judge.py` —— 技能文档已把它登记为判据 ⑬ 的**变异对照夹具**,
删了以后 ⑬ 的红绿对照就没法复现(技能纪律:夹具"跑完即弃"指不占常驻位置,⛔ 不等于删掉证据)。
---
## 主会话 · 核实「行尾符与同源冲突」接手单(11:09~11:20)
**来件**:`C:\Users\Administrator\Desktop\workbuddy_skills-行尾符与同源冲突-交仓库会话.md`
(另一个环境留下的接手单;用户令「**确认是否有相关问题**」⇒ 只要核实结论,⛔ 不动手改)。
**⚠️ 来件与实际不符的一处(先说清)**:单子写本机仓是 `D:\.workbuddy\skills`,
实际**该路径不存在**;D 盘那个仓在 `D:/github/_alotbuy-gitea/workbuddy_skills`(remote 指向 `work.alotbuy.com`,与本机用的不是同一个 origin)。
更要紧的是:**单子称该仓「本地领先远端 2 个提交」`1774693` / `886d99a` —— 这两笔提交在 D 盘仓里不存在**
(`git cat-file` 报 `Not a valid object name`),且该仓 `origin/master..HEAD` 与 `HEAD..origin/master` **双向为空**(既不领先也不落后)。
⇒ **单子里第三条("有 2 笔提交停在本地待同步")在本机无法证实**,⛔ 不作为待办。
**核实结论:三条里第一、二条属实,且第一条比单子描述更严重。**
**① 行尾符 —— 属实,实测读数**(仓 `E:/ProgramData/.workbuddy/skills`):
- `.gitattributes` **不存在**;`core.autocrlf = true`;`.gitattributes` 属性列 **全仓为空**(零覆盖)。
- 全仓入库形态统计:`i/lf` **4576** / `i/crlf` **244** / `i/-text` 28 / `i/none` 5 / **`i/mixed` 1**。
- CRLF 244 个的分布:**`product-planning/references/` 占 237 个**(冲突重灾区),`session-mechanism/scripts` 5 个、
`session-mechanism/references` 1 个、`product-planning/SKILL.md` 1 个。
- 🔴 **单子没提到的一个更糟样本**:`product-planning/references/stage-requirements/references/create-prd.md`
是 **`i/lf` + `w/crlf`**(入库 LF、工作副本 CRLF)⇒ 这正是"本地一改就整文件 diff / 合并整块冲突"的最典型形态。
- 另两处同批读数:`stage-delivery/SKILL.md` = `i/crlf w/crlf`;`stage-discovery/.../grill-me.md` = `i/lf w/lf`。
- **本机当前无受害症状**:`git status` 干净(0 行)、无冲突标记残留 ⇒ **隐患在位、尚未发作**。
- ⚠️ 我自己上一笔提交 `053a6a6` 的 5 个文件**全部 `i/lf w/lf`**,不受影响。
**② `session-mechanism/SKILL.md` frontmatter —— 属实**:
- 实况:第 6 行 `last_change: 2026-10-05 10:5x · 🔴🔴 …` 是一行超长标量,
其后**12 行以 `·` 开头且无缩进** ⇒ 顶层出现无键的行,**不是合法 YAML**(单子描述准确)。
- 同文件其它字段(`name`/`description`/`version`/`updated_at`)写法正常,确属孤例。
**③ 同源冲突(两份各写一版)—— 本机无异常**:
- 单子列的 4 个冲突文件**路径写少了一层**(实际都在二级 `references/` 下):
`stage-delivery/SKILL.md`、`stage-delivery/references/execution-runbook.md`、
`stage-discovery/references/grill-me.md`、`stage-requirements/references/create-prd.md`。
- 本机仓里这 4 个文件**无未提交改动、无冲突标记**(`grep '^<<<<<<<'` 零命中)⇒ 当前是合好的状态。
- 单子那句"动手前先 `git fetch`、同一件事只在一处定稿"是**作业纪律**,本机已在守(本轮推送前就跑了 `git fetch`)。
**待办(⛔ 本轮未动,属另一个仓/需出方案)**:见本轮回复末尾的待拍板项。
---
## 主会话 · 执行「行尾符 + frontmatter」三项修复并推送(11:22~11:40)
用户令:「**修改完成后 检查无误 推到技能仓库**」⇒ 按上一轮三项的**倾向 A** 全做。
**① `.gitattributes`(新文件,仓根)**
- 内容:默认 `* text=auto eol=lf`,再对文本类逐类显式声明(md/py/sh/json/css/html/js/mjs/yml/yaml 等);
**`.bat`/`.cmd` 保留 CRLF**(Windows 批处理对 LF 敏感);图片/字体/压缩包/音视频/数据库一律 `binary`。
- 🔴 **`.gitignore` 同步放行 `!/.gitattributes`** —— 本仓是「先 `/*` 全挡再逐个放行」的白名单模式,
不放行 ⇒ 这个文件根本进不了库;而它跟 `.gitignore` 同属**仓级规则**,必须入库,
否则别的机器取仓后仍按各自 `core.autocrlf` 各行其是。
- 提交 `cf0ec41`(2 files, +68)。
**② 本机 `core.autocrlf` 设 false(立刻止血)**
- `true` → `false`(仓级配置,只影响本仓)。
- 验:`git ls-files --eol` 的 `attr` 列从**空**变成 **`attr/text eol=lf`** ⇒ 属性真的生效了(⛔ 不是只写了文件)。
**③ `--renormalize` 重新规范化(单独一笔)**
- 实跑改动 **245 个文件**:`product-planning` 239 / `session-mechanism` 6。
- 🔴 **关键验收:纯行尾符,零内容改动** —— `git diff --cached --numstat --ignore-cr-at-eol` **输出为空**;
且 `insertions 44412 / deletions 44412` **完全对称**(行尾符转换的数学特征)。
- 复核**没碰二进制与资源**(`grep -E '\.(png|jpg|ttf|woff2?|zip|gz)$'` 零命中)。
- 提交 `9e779c4`。**单独一笔**的理由:把「改规则」与「按规则刷数据」分开,
将来撞车时可直接回退这一笔而不牵连 `.gitattributes`。
- **行尾符终态**:`i/crlf` **244 → 0**;`i/lf` 4822;剩 `i/-text` 28(二进制)+ `i/none` 5。
**④ `session-mechanism/SKILL.md` frontmatter 合法化(内容一字不动)**
- 原形态:`last_change:` 一行超长标量 + 其后 **12 行**以 `·` 开头无缩进 ⇒ 顶层无键行 12 处。
- 🔴 **顺带发现接手单没提的第二处**:`agent_created:` 前面还夹着一行游离的
`· **本包自身**:install.py …` —— 同样是顶层无键行(改前共 **12** 处,接手单只指出了前 11 行)。
- 改法:块标量 `last_change: |-` + 续行统一两格缩进(含那行游离内容)。
- **验收四条(改前 vs 改后逐字比对)**:
① 顶层无键行 **12 → 0**;② `last_change` 正文 **12 行逐字一致**;
③ 正文(第二个 `---` 之后)**逐字一致**;④ **PyYAML 解析通过**,
字段=`name/description/version/updated_at/last_change/agent_created`,`last_change` 3358 字符 / 36 行。
- 提交 `5d16c6d`(1 file, +14/-13)。
- ⚠️ 验证脚本**第一版有 bug**(`split("|-")[1]` 把首段读成空串)⇒ 一度误报"内容不守恒";
修掉解析后复验才通过。**教训:验证脚本自身也要先自证**,否则会把"改动正确"误判成"改坏了"。
**⑤ 推送与终验**
- `git push origin master` ⇒ `d9445b5..5d16c6d`(成功)。
- 终验:本地 HEAD = `origin/master` = `5d16c6df706f63543073ff63c50889dd99c55c81`;三笔提交在远端齐全;工作区干净。
**沉淀(跨项目可用)**:
- 🔴 **白名单 `.gitignore` 模式下,新加仓级文件(如 `.gitattributes`)必须同步放行** —— 否则"写了但没入库",
而且 `git status` 里它显示为 `??`(未跟踪),**很容易被误读成"已经生效了"**。
- 🔴 **行尾符规范化的验收判据 = `--ignore-cr-at-eol` 后零差异 + 增删行数对称**。
只看文件数会误判(246 个文件听着很吓人,实际一个字都没改)。
- 🔴 **验证脚本要先自证**:解析器有 bug 时会产出"看起来很严重"的假红(本轮就撞了一次)。
---
## 两个看板整包搬进会话技能 `boards/`(用户令「1 boards 2 A」)
**用户原话**:「把这两个看板都纳入会话技能中 ,分两个看板文件夹 放在assert中(判断是否应该放在这里)」
→「1 肯定是整包啊 2 就两个看板 对应两个文件夹」→「但是当前情况 是两个看板最好代码和页面放一起,
要不单独开个 workshop 文件夹 放两个看板」→「这里面有html代码适合放到 scripts中吗」→「1 boards 2 A」。
**拍板结果**:文件夹名 `boards`(⛔ 不是 workshop);A = 本区部署与计划任务**本轮一起改**。
**新增第三层分层**(判据,已写进 SKILL.md):
- `scripts/` = 可执行程序(被计划任务/hook 调)|`assets/` = 静态素材(样式、模板)
- 🔴 `boards/` = **自带页面的看板程序**(一个看板一个文件夹,页面 + 脚本同处一包)
- ⛔ **HTML 不进 `scripts/`**:那层是可执行程序层(现有全是 `.py`),
`session-rules-check.py`/`install.py` 会按脚本扫(语法检查、副本比对)⇒ HTML 进去会被误判。
**搬了什么**:
- `boards/会话看板/`(6 文件):`board.py`(150141B)/ `board_ext.py` / `board-launch.py` /
`board-launch.py.tpl` / `board.html`(151811B)/ `board-render-probe.js`(端口 20099)
- `boards/项目管理界面/`(3 文件):`project-board.py` / `project-board.html`(54102B)/
`project-board-launch.py`(端口 20100)
**🔴 搬迁带出的真缺陷(最重要的一条)**:
`board.py` 里 `importlib` 按 `Path(__file__).parent / "collabd.py"` 取判据 ——
**「与 collabd.py 同目录」这个前提搬走后不成立** ⇒ 静默落到 **except 分支里本地抄的那份判据**。
危害:判据可能漂(同族"共用同一实现"当场 FAIL),**但看板照跑、肉眼完全看不出来**。
修法:候选表(同目录 → 包内 `scripts/` → `DSH_SKILL_ROOT/scripts/`)+ 兜底分支
**写 stderr 留痕**(⛔ 不许静默降级)。`CFG_P` 同病同治(对齐 `collabd.py::load_cfg()` 查找顺序)。
**selftest.py 的三处假设修正**(都源于"一个路径指两处"):
- `t_scope_parity` 的子进程代码用**一个 `H`** 同时指 board.py 与 collabd.py ⇒ 拆成 `H`/`B` 两参数
- `selftest.py` 里 `HERE / "board.py"` 27 处 ⇒ `board_py()`(带旧位置兜底)
- `HERE.parent / "assets" / "board.html"` 15 处 ⇒ `BOARD_DIR / "board.html"`
**顺带修一条既有 FAIL(与搬迁无关)**:判据锚点没跟文档改 ——
`supervise-persistence.md` 的措辞在 `053a6a6` 已改成「排期按点拉起主会话这条路不存在」,
判据仍锚旧句「排期代替不了载体」(那句现在只在 `SKILL.md` 里)⇒ 恒红。
**验收读数**:
- `--filter 看板`:**PASS 110 / FAIL 0**(另 1 条报告型不计入)。迭代轨迹 92/18 → 104/6 → 105/5 → 109/1 → **110/0**。
- 全量:**PASS 91 / FAIL 1**(同一条报告型「产物落点」,实质 FAIL 0)。
⚠️ 「合计」行缺失的真因=`safe-delete` 安全闸在收尾时拦了 64 个清理目标(> 阈值 50)
⇒ 本机工具层策略,⛔ 与技能无关。
- 会话看板 `board.py --out` ⇒ **50515 字节**快照,**无「回落本地判据」告警**(证明真从 collabd.py 取)。
- 项目管理界面 `--once` ⇒ 4 个项目全列出;HTTP `/` 200 · **54102 字节**
(=技能包真身那份 ⇒ 证明工作区侧确实是转发);`/api/projects` 出数据;`/healthz` ok。
**工作区侧改成薄转发**(⛔ 不留第二份实现):
- `交付物/项目管理界面/project-board.py`:改成薄转发(原 38380B 完整实现 → 转发壳)
- `交付物/项目管理界面/project-board.html`:改成**路标页**(54KB 真身 → 说明指向)
- `交付物/项目管理界面/project-board-launch.py`:上一轮已改薄转发
- 原件备份 ⇒ `tmp/pm-board-bak-20261014/`
- 🔴 **计划任务不用改**:动作指向工作区启动器(薄转发会转到技能包真身)⇒ 稳定路径正是留这一层的目的。
重起后 pid **35080 → 23596**,命令行仍是 `pythonw.exe ".../project-board-launch.py"`。
**入库**:提交 `52566c7`(9 新文件 + 3 改名 + 6 修改),推送 `5d16c6d..52566c7` 成功。
- 🔴 `.gitignore` **无需放行** `boards/` —— 白名单放的是整包 `!/session-mechanism/`,包内新增目录自动被包含。
- 凭据扫描:`token` 命中全是文案里的普通词(「0 token」「SyntaxError: Unexpected token」),**无真凭据**。
- 行尾符:`git ls-files --eol` 全部 `i/lf w/lf attr/text eol=lf` ✅。
**沉淀(跨项目可用)**:
- 🔴 **搬家会静默破坏"同目录"隐式前提** —— 这类 import 都有 `except` 兜底,
于是**行为看着正常、判据却已漂**。判据=「搬迁后必须有一条用例专门验『真的从模块取、不是本地抄』」;
正解 = 兜底分支**必须留痕**(⛔ 宁可见地坏,不可静默降级)。
- 🔴 **一个路径常量指两处 = 下一次搬家的地雷**。判据:凡 `HERE / "x.py"` 里 `HERE` 是"包根/脚本目录"
这类**位置代称**,就该问一句"另一个文件搬家时它还成立吗"。
- 🔴 **判据锚点必须逐字抄原文**(本轮第三次栽):文档改措辞后判据恒红,
⛔ 别把"判据红"直接读成"实现坏了"——先问"是不是锚点过期了"。
---
## 第二件:会话技能包内代码一律相对路径(用户令)
**原话**:「会话技能中 能用相对路径的都要用相对路径」
**新立判据**(SKILL.md §0 + 第 11 条禁令)—— 问一句「**这个根能不能从『我自己的位置』推出来?**」:
- **能**(本包自己的目录)⇒ 从 `__file__` 上跳推导(`Path(__file__).resolve().parent` 上跳 N 层)。
- **不能**(工作区根/配置目录/文档库根 —— 与技能包无任何相对关系)⇒ 读本包 `env/roots.env`。
- **推不出来** ⇒ **如实报错 + 留痕**。⛔ 不许静默扫空;⛔ 也不许回落到一个猜的盘符。
- ⚠️ **文档/注释里的绝对路径不在此列** —— 举例、历史取证照写,那是证据。
**落的四处改动**:
- `boards/项目看板/project-board.py`:新增 `_sm_load_roots()`(`range(5)`);
`_SKILL_ROOT` 从 `HERE.parent.parent` 推导;`SKILL_BOARD_DIR` / `SKILL_SCRIPTS` 同源推导。
- `WS_ROOTS` **从常量改函数** `_ws_roots()`:`DSH_PM_BOARD_ROOTS` → `DSH_AIROOT` → 由自身工作区推父目录,
三级回落;全空 ⇒ `to_project_view()` 里**如实报错 + 写 `logs/project-board.err.log`** 后返回,⛔ 不静默扫空。
- `scripts/selftest.py`:生产区定位**改扫兄弟区**(去写死区名,靠 `DSH_AIROOT` 遍历找 `.workbuddy/collab/collabd.config.json`);
两处测试假路径去盘符(`__selftest_no_such_ws__` / `__selftest_ws__`)。
- `env/roots.env.example` 入库,真文件由 `install.py --apply` 生成、**不入库**。
键:`DSH_WS_ROOT` / `DSH_AIROOT` / `WB_RESULT_HOOK_WS` / `COLLABD_PROD_CONFIG` /
`CODEBUDDY_CONFIG_DIR` / `DSH_DOCS_ROOT`。注入用 `setdefault`(宿主 env 优先,本文件兜底)。
**换机场景实测(两条都对)**:
- 只带包、不带 `env/` ⇒ **如实报错**(不瞎猜盘符)。⚠️ 我第一次"模拟换机"只清 env 变量是**假验证**
——`env/roots.env` 文件还在,照样读得到 ⇒ 必须把包拷到 `/tmp` 且**不带 `env/`** 才验得出真行为。
- 只给 `DSH_AIROOT` ⇒ 正常扫到 **4 个项目**。
**禁令条数**:「十条禁令」→「**十一条禁令**」(含第 0 步里那句"在十一条禁令之前"一并订正)。
---
## 第三件:两个看板定名(用户令)
**原话**:「会话技能的两个看板 改名 会话看板 和 项目看板」
**定名**:**会话看板**(`boards/会话看板/`,端口 **20099**)|**项目看板**(`boards/项目看板/`,端口 **20100**)。
原「项目管理界面」这个叫法**全链退场**。
**改了什么**:
- 技能包目录:`boards/项目管理界面/` → `boards/项目看板/`
- 工作区目录:`交付物/项目管理界面/` → `交付物/项目看板/`(三份薄转发/路标页跟随)
- 包内三处路径引用 + **展示名 6 处**(模块 docstring、错误日志前缀、`argparse description`、启动横幅);
`project-board.html` 的 `<title>` → `项目看板 · 执行任务`、`<div class="brand">` → `项目看板`。
- 工作区三文件内「项目管理界面」全改(含功能性路径 `BOARD_DIR`/`TARGET`),**`remain=0`**。
- 计划任务 `dsh-project-board-keepalive`:动作与 CWD 同步指向 `交付物/项目看板/project-board-launch.py`。
- `references/manifest.md`:目录名 + 四行 md5/尺寸更新。
**端到端验收**:服务重起 pid **23596 → 40776** · HTTP **200** · **54102 字节** ·
`<title>项目看板 · 执行任务</title>` · `/api/projects` 出数据 · `/healthz` ok。
`--filter 看板`:**PASS 110 / FAIL 0**。
**入库**:
- 技能仓提交 `fe22f4c`(git 识别 R096/R099/R089 重命名;凭据扫描无命中;行尾全 `lf`)。
- 工作区仓提交 `6b783aa`,推送 `df9dcfb..6b783aa`。
(暂存清单 R092/R082/R093 —— 三文件全被识别为重命名,非删+增。)
**沉淀**:
- 🔴 **改名是链式动作,不是 `mv` 一下** —— 完整链:改目录 → 改包内引用 → 改展示名 → 改 HTML 标题/brand
→ 改工作区薄转发 → 改计划任务动作与 CWD → **重起服务验端到端** → 入库。
漏掉"重起"这一步,改名只落在磁盘上、线上还是旧名(HTTP 标题是**最硬的验收点**)。
- 🔴 **工作区侧留薄转发层的价值这轮兑现了** —— 计划任务指工作区稳定路径,
技能包真身怎么搬、怎么改名,任务都不用动。
---
## 第四件:提交前复核 ⇒ 抓出「相对路径」又漏 2 处(用户令:「检查确认修改无误 功能正常后 提交仓库」)
**复核方法(可复用)**:不是重看自己改了什么,而是**用判据反查全包** ——
`git grep -nE '"[A-Za-z]:/|[A-Za-z]:\\\\' HEAD -- 'session-mechanism/**/*.py'`,
再**逐条判读**(注释/docstring 里的举例属禁令例外,真代码常量才算)。
**判读结果**:20 处命中里 18 处是注释/docstring 举例(照写正确),**2 处是真漏网**:
① 🔴 `scripts/hooks/wb-result-hook.py` 的 `CARE_PREFIX` —— **真代码常量**
原 `= "e:/programdata/aiproject/"`(盘符写进代码)。它是**域外过滤**的判据:
判错的后果不是"慢",而是**把自家会话判成"域外"⇒ 不读转录**,且**从外面看毫无异常**。
修法:新增 `_sm_load_roots()`(`scripts/hooks/` 到包根上跳**三级**),
`CARE_PREFIX` 改从 `DSH_AIROOT` 推导;取不到 ⇒ **空串**,
由 `classify()` **如实报错 + 打 stderr 留痕**(⛔ 不静默把全部会话判成域外)。
② ⚠️ `scripts/selftest.py:6875` 假样本 `"workspace": "E:/selftest"` ⇒ 改 `"__selftest_ws__"`
(与同段已改的两处同族,属"测试假数据也别带盘符"的一致性收口)。
**⚠️ 我中途自己踩的坑(记下来)**:第一次改 `CARE_PREFIX` 拼成 `"/" + …`(**多一个前导斜杠**),
于是 `startswith` 一条都匹配不上 ⇒ **场景①全部判成 `outside`**。
若不复核"逐样本对账"、只看"跑通了 rc=0",这个错误会直接进库。
⇒ 判据:**改路径拼装后必须逐样本对账输出**(本机 5 个样本 + 1 个空值),⛔ 别以 rc=0 为准。
**验证(三层,缺一不可)**:
- 有 `DSH_AIROOT` ⇒ 分类**逐样本与改前硬编码完全一致**
(`home` / `line` 客户端线 / `line` ai1net_ui / `other` contentm_agent / `outside`)。
- **包不带 `env/`**(拷到别处)⇒ **如实报错 + stderr 留痕**,⛔ 不静默(真·换机场景)。
- **清空 `DSH_AIROOT`/`WB_RESULT_HOOK_WS`/`COLLABD_PROD_CONFIG` 后用真实 payload 干跑 hook**
⇒ **rc=0 且正常输出 JSON**(证明 `_sm_load_roots()` 从包内 `env/roots.env` 兜底成功
—— 这正是加它的目的:**宿主不给 env 也能跑**)。
**顺带查清两件"看着像问题、实则是设计"的事**:
- `.gitignore:54` **明确排除** `references/manifest.md` ⇒ 它是本机生成产物(各机 md5 可能不同),
工具 `install.py --manifest` 可重算(本轮重算 80 份 / 语法失败 0),但**不入库**(⛔ 别指望它进提交)。
- `references/pitfalls.md:3449` 命中「项目管理界面」⇒ 那是 **P0-89 的历史取证记录**(2026-10-06 发生的事),
按第 11 条禁令的例外**照写不改**。
- 工作区那 78 项脏改动**与本轮无关**:49 个 `M` 是**纯行尾符**(`i/lf` ↔ `w/crlf`,
`insertions == deletions` 即内容零变化,前序遗留、`.gitattributes` 不存在);
其余是 `automations/*/` 运行时产物与别路工作留下的未跟踪目录。
**入库**:技能仓提交 `7a08cd3`,推送 `fe22f4c..7a08cd3`(仅 2 文件 / +49 −3)。
自测全量 rc=0 · **PASS 110 / FAIL 0** · 语法失败 0。
**沉淀(可直接复用)**:
- 🔴🔴 **"能用相对路径"这条令,一轮清不干净 —— 必须用 grep 反查全包,并逐条判读**。
本轮 20 处命中里混着 18 处"合法举例",**只有靠人判断才能把 2 处真漏网挑出来**;
纯自动替换会误伤注释(把证据也改掉)。
- 🔴 **路径拼装改完必须"逐样本对账"** —— 我这轮自己多拼了个前导 `/`,
rc=0、不报错、看着一切正常,**只有逐样本比对才照出来**。
- 🔴 **`setdefault` 对空串不生效**(`DSH_AIROOT=""` 时不会被 `env/roots.env` 覆盖)——
这是**全包统一口径**(十余处同写法)⇒ 属既有设计,⛔ 不单方面改一处(会破坏单一事实源)。
- 🔴 **宿主不给 env 也要能跑**:hook/常驻这类由宿主起的进程,`env/roots.env` 兜底是**必需项**,
且可用"清空相关 env 后干跑"直接验证。
---
## 第五件:清理 tmp/ 一次性残留(用户令:「历史无效内容 改删除就删除」)
**清理原则(判据)**:只删**一次性验证残留**与**过期快照**;
凡**活区**(程序正在读写的)或**实质内容**(该入库的)一律保留。
**删掉的(tmp/ 顶层 34 项 → 3 项)**:
- 今日探针与日志 16 份(`board-*.json` / `probe-board.json` / `pid35080.json` / `pm-port-check.txt` /
`task-*.txt` / `selftest-*.log` / `_idle_hook_probe.jsonl` 等)
- 临时脚本 11 份(`procchain*.py` / `restart*.py` / `asar-*.js` / `verify_launch.py` 等)
- 过期备份/中间态快照 5 个目录(`pm-board-bak-20261014/` / `bak-*` / `envsplit-{before,after}/`)
- 过期日志与截图 4 份(`selftest-round3.txt` / `board-serve.err.log` / 两张看板截图)
- 探查笔记 1 份(`会话批量归档删除-排查-20261014.md`)
**保留的(关键判读,⛔ 差点误删)**:
- 🔴 `tmp/supervise-inbox/` —— **活区**(`board.json` / `collabd-state.json` / `_token-deliver.*` 每分钟在写),43 项。
- 🔴 `tmp/supervise-bg.log` —— **常驻程序的运行日志**(含 `safe-delete` 拦截记录、`collabd.py` 告警),仍在追加。
- 🔴 `tmp/selftest/` —— 自测工作区。
- 🔴🔴 `docs/pm/ai1net-plugin/research/`(9 份产品调研四段式文档:1a 需求/1b 竞品/1c 画像/1d 策略/1e 场景)
与 `执行会话/目标-本机协作-2f3331/`(152 份执行产物)—— **看着像"未跟踪残留",实则是"该入库但还没入库"的实质内容**。
两处**都未被 gitignore**。⛔ 绝不能当无效内容删。
**工具限制(本机实测)**:
- ⛔ **回收站两条路都被安全策略拦**:`Add-Type`("compiles and loads .NET code at runtime")
与 `New-Object -ComObject Shell.Application`("COM object instantiation can run arbitrary code")。
⇒ 退回**直删**(符合既定口径:「仅有强制拦截的沙箱场景才走直删」);且都是 `tmp/` 内(`.gitignore:5` 排除区)
⇒ **不影响仓库**,删前逐项核对过内容。
**验收**:清理后 `tmp/` 只剩 3 项(全是活的);项目看板 `:20100` HTTP **200** · `/healthz` `{"ok":true,…}`;
`git status` 仍是 78 项(与本轮清理无关)。
**沉淀**:
- 🔴 **"这是残留还是内容"要按"它在不在入库范围"判,⛔ 不按"它长得像不像临时文件"判**。
本轮 `docs/pm/research/`(9 份正式调研文档)与 `执行会话/…`(152 份产物)都是**未被 ignore 的未跟踪目录**,
长得像残留、实则是**待入库的成果** —— 若按"看着像垃圾"删,等于删掉别路的工作成果。
- 🔴 **未跟踪(`??`)分两种**:① 运行时产物(`automations/*/`,可删)② **待入库成果**(`docs/pm/`,⛔ 不可删)。
判据=**`git check-ignore` 有没有命中** —— 命中的是运行时产物,没命中的是**该由人决定去留**的。
- 🔴 **删前必查"它是不是活区"** —— 本轮 `supervise-inbox/`(每分钟在写)与 `supervise-bg.log`(追加中)
都长得像"一堆老文件",实则是机制正在用的。判据=看 mtime 有没有近期变动。
---
## 第六件:技能根绝对路径清零(用户令)
**用户原话**:「SKILL.md:189 判据源写 E:/ProgramData/.workbuddy/skills/ 严格禁止技能中写这里绝对路径(除非跨根目录因引用),跨环境全部报错」
**定位过程(⚠️ 记下,因为绕了弯)**:用户只给了「SKILL.md:189」,
但**本包(session-mechanism)的 189 行不是这个内容**(是「机制新建会话用哪个模型」)。
⇒ 逐个技能包扫「各 SKILL.md 的第 189 行」,在 **`product-planning/SKILL.md:189`** 命中:
「**判据来源**(单一可信源…):`humanizer`…、`humanizer-zh`…。**两份技能都在 `E:/ProgramData/.workbuddy/skills/`**」
—— 「判据源」正是用户说的那三个字。**教训:用户给的行号 + 关键词同时指认时,先按关键词全仓找,别只认行号。**
**判据(用户给的两分法,全仓统一)**:
- **技能根**(本包自己 + 同根技能)⇒ **一律不写绝对路径**,写 `<技能根>/<技能名>/`;
- **跨根引用**(工作区根 / 文档库根 / 本机软件安装位 / 宿主数据目录)⇒ 才落到具体位置,优先读环境变量。
**🔴 这条推翻了昨天的口径**:session-mechanism `SKILL.md` 原写「⚠️ 文档/注释里的绝对路径**不在此列**(举例、历史取证照写 —— 那是证据)」
⇒ 用户新令把例外**收窄到只剩「跨根引用」**,文档/注释/留痕里的**技能根**路径同样不许写。已改写该条。
**改了什么(进仓 10 文件 · 19 增 18 删 · 提交 `2f65609`,推送 `7a08cd3..2f65609`)**:
- `product-planning`:`SKILL.md`(判据来源 / video-capture 位置 / 本技能位置 + **新增「路径写相对或占位」规则**)、
`references/_留痕/SKILL-总入口-20261007改说人话前.md`、`stage-delivery/assets/video-capture/SKILL.md`、
`scripts/check_naming.py`(注释)
- `session-mechanism`:`SKILL.md`(反例改占位 + 例外条款改写)、`references/deploy.md`(安装目录)、
`scripts/session-rules-check.py`(docstring)、`boards/会话看板/board-launch.py.tpl`(模板占位符)
- `video-capture/ATTRIBUTION.md`、仓根 `改包纪律.md`(3 条 `cd` 命令)
**另外改了但⛔ 不入仓的(15 处)**:`dsh-knowledge`(5+1+1)、`dsh-workflow`(5+1+2)
—— 🔴 **这两个包被 `.gitignore:9` 的 `/*` 全挡,且没有自己的 `.git`** ⇒ 改动只落在本机,不进任何仓。
**🔴🔴 本轮最大的坑(必记)**:**`Grep` 工具(ripgrep)默认跳过被 `.gitignore` 排除的文件** ——
我第一遍用它扫「技能根路径」,报「No matches / 已清零」,**结论是错的**;
改用 `grep -rn … --exclude-dir=.venv` 重扫,才照出 `dsh-knowledge` / `dsh-workflow` 里那 15 处。
⇒ **判据:核实"某形态在技能包里清干净没有",一律用 `grep -r`,⛔ 别用会尊重 ignore 的搜索器**
(被 ignore 的文件恰恰是"不在仓里、最容易失控"的那批)。
**验收**:
- `product-planning` 命名闸门:**通过 2060 / 失败 0**
- `session-mechanism` 全量自测:**PASS 110 / FAIL 0**(确认没有判据锚定我改写的那句例外条款)
- 全仓 `grep -r` 反查:清零。剩余两处均属正当例外 ——
`session-mechanism/install.log`(运行时产物,`.gitignore:32` 已 ignore)、
`browser-harness/.venv/**/direct_url.json`(pip editable 安装元数据,改了会破坏 pip 认知)。
- ⚠️ 编辑器把 3 个文件写成了 CRLF ⇒ 已转回 LF(**3 个文件各 260~440 处 CRLF,说明它们原本就是整文件 CRLF**,
但 diff 未受污染 —— `.gitattributes` 的 `text eol=lf` 在比较时做了规范化,diff 仍只有我改的那几行)。
**沉淀(可复用)**:
- 🔴 **"不写死路径"这条令,落地要分两个根**:**自己的根**(可推导 ⇒ 禁止写)与**别人的根**(推不出来 ⇒ 允许外置引用)。
判据一句话:**这个根能不能从「我自己的位置」推出来**。
- 🔴 **清一类坏形态,必须先"全量反查"再动手** —— 且反查工具的**过滤行为**本身就是变量(见上面 ripgrep 那条)。
- 🔴 **改完规则文件要跑全量自测**:本轮改写了 SKILL.md 的例外条款,若某判据锚定那句就会恒红
(同族坑今天已栽过两次)—— 实测未被锚定,全量 PASS 110。