Files
dsh_ai1net_server/.workbuddy/memory/2026-10-11.md
T
admin d933181a55 记忆:技能仓已配上游并推送 d81f75f(用户选 A)
推前核对:顶层 6 个技能 + 改包纪律.md 符合白名单;远端 90cc431 → 本地 d81f75f 快进。
远端复核:origin/master = d81f75f;env/ 下只有两个 *.example,没有任何配置真身。
2026-10-11 21:53:29 +08:00

288 lines
25 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-11 工作日志
## 核对项目看板(20100)「执行中」列 vs 真源 —— 列映射没错,真源有陈旧值(10:2x)
**用户问**:「确认 项目看板中 执行中的状态对吗」
**取数**:看板 `/api/projects` 现取(10:17)+ 各工作区 `tmp/supervise-inbox/goal.json` 与 `goals/*.json` 逐条比对。
- 看板「执行中」= **4 张**,全部 `state=active`、`life=进行中`:
· vibe-product「把产品规划技能整理成唯一形态」(10-05T18:04 声明;4 条验收判据**全「未过」**)
· contentm_agent「左侧导航改回基准形态 64px 图标在上」(10-10T22:32;3 条判据全未过)
· contentm_agent「逆向 Easel 热点雷达与选题链路 补齐现版逻辑」(10-10T22:39;3 条判据全未过)
· aigc-idea-impression「获取B站博主AI生成界面与设计教程视频…」(10-11T04:23;**当前目标**,5 条判据「本轮尚未开始」)
- 真源里 `lifecycle=进行中` 的也是这 4 个 ⇒ **看板分列逻辑没错**,它如实反映了真源。
**根因(读 `goalctl.py:669-677` 坐实)**:`--switch-goal` 的「自动复位」作用在**新目标**(`new.lifecycle="进行中"`),
旧 `goal.json` 是**原样归档**进 `goals/` ⇒ 归档件保留**被替换那一刻**的 lifecycle。
⇒ **换目标时不收尾旧目标** ⇒ 没做完的旧目标带着「进行中」进归档 ⇒ 看板「执行中」列虚高(4 张里 3 张已无人推进)。
⚠️ 只是显示问题:机制判「要不要拉检查会话」读的是当前目标 `goal.json`,⛔ 不受归档件影响。
**🔴 我差点搞错的一处(记下,防再犯)**:归档件 `lifecycle_why` 写「换目标 ⇒ 复位(改前=已完成)」,
其中 **`改前` 指前一个目标的状态**(`new` 从旧 `g` 深拷贝后再复位),⛔ **不是这张卡自己的历史值**。
我一度据此判「2 张原本已完成」,读代码后纠正 —— 这三张的 `acceptance_state` 全是「未过」,它们**确实没做完**。
**待拍板**:修法 A(`goalctl` 换目标时把未完成的旧目标置「已暂停」,看板补「已暂停」列)/
B(只改看板,归档件打「已归档」徽标)/C(不动,认为"没做完就该在执行中")。倾向 A。
---
## ✅ 方案 A 已落地:换目标 ⇒ 旧目标收尾「已暂停」+ 看板把它归入「待执行」(10:25–10:30)
**用户定案(原话)**:「这类就属于暂停状态 可以归入到 待执行」+「1、方案A ,需要优化项目机制」。
**改了三处**:
1. `~/.workbuddy/skills/session-mechanism/scripts/goalctl.py`(`--switch-goal` 的归档步**之前**):
旧目标 `lifecycle` 不是「已完成」⇒ 置「已暂停」+写 `lifecycle_at/by/why`(why 里记「改前=…」);
⛔ 原本「已完成」的保持不动。改前只复位**新目标**、旧目标原样归档 ⇒ 状态失衡。
2. `交付物/项目管理界面/project-board.py`:`LIFE_TO_COL` 加 `"已暂停": "waiting"`;
`_LIFE_NOCOL` 清空(原先装「已暂停」);注释与 `life_unknown` 口径同步。
3. **存量回填**(一次性):所有工作区归档件里未收尾的 **3 份**置「已暂停」
—— vibe-product ×1(把产品规划技能整理成唯一形态)、contentm_agent ×2(左侧导航改回基准形态/逆向 Easel 热点雷达);
原件备份在 `tmp/bak-暂停态回填-20261011/`(3 份)。
**验证(实测读数)**:
- `py_compile` 两文件通过;技能包全套自测 **PASS 109 / FAIL 0**(与基线一致,含「换目标 ⇒ 新目标复位进行中」那条)。
- 看板服务已重启(新 pid 30252;20100 LISTENING + HTTP 200)—— 改的是服务端 `.py`,必须重启才生效。
- 分列复核:`ai1net-dsh-server active=0` | `vibe-product active=0 waiting=1 done=3` |
`aigc-idea-impression active=1`(当前目标,真在跑)| `contentm_agent active=0 waiting=2 done=10`。
⇒ **「执行中」从 4 张降到 1 张**,留下的那张是真在推进的。
- 技能包清单重算:78 份文件、语法失败 0。
**未做**:技能仓未推(未明确要求 ⇒ 不 commit/push);工作区 `MEMORY.md` 状态层未动(余量只剩 1 字符)。
**锁**:已释放(`dsh.py close` 反序释放,`--release-exec` ✓)。
---
## 项目看板界面两处微调 + 答「删除按钮是做什么的」(10:30–10:35)
**用户两条**:①「E:/ProgramData/AIProject 工作区 这几个字可以删除」②「工作区下拉框后面的删除是做什么用的?」。
**改动**(`交付物/项目管理界面/project-board.html` —— 服务每次请求都 `PAGE.read_bytes()` ⇒ ⛔ 不用重启,curl 实测立刻生效):
- 删头部根目录提示:`<small id="ws">` 元素 **+** 它的 JS 赋值行(必须同时删 —— 只删元素会让 `getElementById("ws").textContent` 抛 null)。
- 删下拉框的可见标签「工作区」;改用 `title="选择工作区"`(`aria-label` 本来就有)保住可用性。
- 清死代码:CSS `.brand small{…}`。
**答第 2 问(删除按钮)**:`btndel` 只在**选中某个具体工作区**时可用(选「全部」时 `disabled`,见 `renderAll()`);
点击后 `prompt` 选粒度 —— 输入 `1` = 只删目标登记(工作区与台账保留、**可逆**、项目从列表消失);
输入 `2` = 整个工作区**送回收站**(不永久删、可从回收站还原);不输入/取消 = 不做任何事。
**流程瑕疵(自记,防再犯)**:本轮动文件**没先抢锁**(上一轮已 `--release-exec`,期间锁空闲)⇒ 事后补了一次 claim→release 记账。
**当时锁况**:另有一把属他人 —— `[执行]-[B站AI设计教程采集]-承接队列`(会话 `7f09203a`,域 `aigc-idea-impression/执行会话`,10:30 起),
按 R9 未动;**它恰好印证**上一轮改动是对的:看板「执行中」剩的那张卡,确实有执行会话在推。
---
## 🔴 交接:项目看板两项新需求(2026-10-11 11:15 · 本会话撞 220K 阈值,按 `CODEBUDDY.md §G` 交接)
**本会话已完成(结论)**:
- 看板「执行中」列失真已修:`goalctl.py` 换目标时把旧目标收尾为「已暂停」⇒ 看板把它归入「待执行」(用户拍板=方案 A)。
存量回填 3 份、看板服务已重启、「执行中」从 4 张降到 1 张;技能包自测 PASS 109 / FAIL 0。
- 看板头部「E:/ProgramData/AIProject 工作区」两处文字已删(含那条 JS 赋值行与死 CSS `.brand small`)。
**遗留(未做)**:技能仓未推(`session-mechanism/scripts/goalctl.py` 的改动只在本机);工作区未 commit。
**下一步 = 用户 11:14 原话两条**:
> 「所有目标项目都需要 加上时间(最后状态的时间)」
> 「已完成显示前8各,其余点击更多 到项目目标列表页查看」
1. **所有目标卡加「最后状态时间」** —— 数据源已在接口里(`/api/projects` 的 `cards[].life_at`,形如 `2026-10-05T12:35:11`);
⚠️ 别拿 `declared_at`(登记时间)顶替,用户要的是**最后状态的时间**。落点与格式自定。
2. **「已完成」列只显示前 8 张**,第 9 张起收进「更多」入口 ⇒ 点它进「项目目标列表页」看全量。
📌 该列表页**当前不存在**(现形态=四列卡片 + 点卡开右侧抽屉),要新建一个列表视图;形态用户没细说,拿不准就先给一版最直接的。
🔴 只截「已完成」这一列,其余三列照旧全显。
**涉及文件**:`交付物/项目管理界面/project-board.html`(界面)/`project-board.py`(取数 `to_project_view()`)。
**服务**:`127.0.0.1:20100`,由 `project-board-launch.py` 拉起 —— 改 `.html` 不用重启(服务每次请求读盘),改 `.py` 要重启。
---
## ✅ 看板两项新需求已落地:每卡加「最后状态时间」+「已完成」列收前 8 张 + 项目目标列表页(11:19–11:35)
**用户原话(11:14)**:「所有目标项目都需要 加上时间(最后状态的时间)」/「已完成显示前8各,其余点击更多 到项目目标列表页查看」。
**只改了 1 份文件**:`交付物/项目管理界面/project-board.html`(⛔ `project-board.py` **零改动** ⇒ **不用重启服务**)。
理由:`cards[].life_at` 早在接口里(`to_project_view()` 已给),列表页用页内视图 + 现有数据 ⇒ 不需要新接口。
1. **最后状态时间**:`_whenBadge(t.life_at)` 做成 `.badge.when` 小标挂在卡片 meta 行(`fmtWhen` ⇒ `2026-10-05 12:35`)。
🔴 取 `life_at`(=`goal.json::lifecycle_at`),⛔ 不是 `declared_at`;实测两者能差好几天(contentm_agent 一张卡 登记 10-10 22:24 / 状态 10-10 09:35)。
⚠️ 空值 ⇒ 显式印「**未记录**」(warn 色):实测 `aigc-idea-impression` 当前目标 `lifecycle_at` 就是空的 ⇒ ⛔ 不拿登记时间顶、⛔ 不静默留白。
2. **「已完成」收前 8 张**:`DONE_LIMIT = 8`;只对 `key==="done"` 生效,第 9 张起换成「更多(还有 N 个)→」。列头计数徽标仍是**该列总数**(14)。
3. **项目目标列表页**:新增**页内视图 + URL hash 路由**(`#/goals/<列>`),`#cols` 加 `.aslist` 切 block(⛔ 不加会被塞进 4 列 grid 的第 1 列)。
内容=状态筛选 chip(全部/待执行/执行中/有阻碍/已完成,计数从服务 `cols` 派生)+ 行(目标名/状态/当前/**最后状态时间**/工作区),按 `life_at` 倒序;点行开同一套详情抽屉;「← 返回看板」回 `#/board`(顺手 `closeDetail()`,否则遮罩盖着点不到)。
🔴 摊平逻辑抽成 `_collectCards()`,看板四列、单工作区视图、列表页**共用这一份**(⛔ 不各写一遍)。
**验证(真实浏览器 · browser-harness,9223 已有实例,未新起)**:
- 全部视图:四列等宽 403px(未动列宽);`待执行3/执行中1/有阻碍0/已完成**8/14**`;`.more` 只出现在已完成列;每张卡都有时间小标。
- `未记录` 兜底实测命中(执行中那张),计算色 `rgb(240,160,32)` ✓。
- 点「更多」⇒ `#/goals/done`、14 行、chip `全部18/待执行3/执行中1/有阻碍0/已完成14`;切「全部」⇒ `#/goals/all`、18 行;点行 ⇒ 抽屉开、标题对。
- 单工作区视图(contentm_agent,done 10>8)⇒ 收 8 张+「更多(还有 2 个)」;列表页范围正确收窄到该工作区(10 行、`范围 contentm_agent · 共 12 个目标`)。
- 非法 hash `#/goals/zzz` 不炸(回落 `all`);返回看板 ⇒ 4 列、`aslist=false`;跑满一轮 3s 轮询后 `window.__errs` 为空。
- 内联 JS 过 `vm.Script` 语法编译(28398 字符)。
- 截图:`tmp/看板-已完成收起前8.png`、`tmp/项目目标列表页.png`。
**流程瑕疵(自记)**:`scripts/dsh.py open` 的预检 **rc=1 拒开工** —— 它把 `交付物/项目管理界面/*` 判成【D】未归类
(`preflight-lock.sh` 的白名单只认 `^(src|poc|test|web|docs|scripts|交接单|skills)/`)。判定属**脚本白名单缺项**(交付物本就属 `ai1net-dsh-server/` 域),
未擅自改该脚本(它是【E】机制层、须独占),本轮按提示改为直接 `handoff-guard.sh --claim-exec --domains ai1net-dsh-server/` 抢锁。
⇒ 记一笔:**凡动 `交付物/` 下文件,入口预检必失败**,可考虑给白名单补 `交付物`(属机制层改动,未做)。
**锁**:已释放(`dsh.py close` 反序释放 ✓)。**未做**:未 commit/push(未明确要求)。
---
## 去掉工作区下拉后面的「删除」按钮(2026-10-11 11:32–11:38)
**用户令**:「去掉工作区后的删除功能」。
**改动**(只有 `交付物/项目管理界面/project-board.html` 一份;⛔ `.py` 未动 ⇒ 不用重启 —— 改 `.html` 服务每次请求读盘):
1. 删按钮元素 `<button id="btndel" …>删除</button>`(头部、下拉框右侧)。
2. 删它的两处联动:`renderAll()` 里的 `disabled = true`、`renderOne()` 里的 `disabled = false`。
3. 删 `doDelete()` 整个函数(原先按 `prompt` 选粒度:1=只删目标登记、2=整个工作区送回收站)。
4. 删 `btndel → doDelete` 的事件绑定。
5. 删 CSS `button.danger{…}`(全文件只这一个按钮用它)。
⇒ 五处各留一行注释留痕。
**保留未动**:后端 `/api/delete` 端点与 `act_delete()` —— 界面已无入口,留着不影响;删它要动 `.py` 并重启服务,没必要。
**验证**:全文件只剩 3 行注释含 `btndel`/`doDelete` 字样、无活引用;线上页面(curl 实测)头部只剩 `[下拉] … +新建目标 [刷新]`,删除按钮已消失。
**流程**:抢域锁 `--domains ai1net-dsh-server/` → 改 → `--release-exec` 已释放。
**并发确认**:改的是接续会话 11:22 刚交付的同一份文件(它做完「加时间+已完成收前 8 +列表页」)⇒ 动手前先确认它的锁已释放、文件 11:22 后无新写入,无冲突。
---
## 技能库待提交清单盘点(18:53 · 只读)
**用户问**:「技能文件有哪些改动需要提交」。相对 HEAD `fd9c902` 共 **8 项**(7 个已跟踪文件 + 1 个未跟踪目录,合计 +59/−294):
- `browser-harness/SKILL.md`(今天 10:19):标签页按**页面类型**分(一类页面一个、异类各开一个),依据 10-11 用户原话。
- `product-planning/SKILL.md`(16:38)+ `…/stage-delivery/assets/video-capture/SKILL.md`(16:38,改写成「交接说明」)+ 删两脚本(`extract_frames.py` −156、`fetch_video.py` −85)
⇒ **video-capture 今天独立成全局技能**。
- 未跟踪:`product-planning/references/_留痕/video-capture-已独立-20261011/scripts/`(上面两个脚本的原件留档)。
- `session-mechanism/scripts/collabctl.py`(04:15,+2):登记 `aigc-idea-impression` 工作区的常驻。
- `session-mechanism/scripts/goalctl.py`(10:26,+14):**本会话改的** —— 换目标时旧目标收尾「已暂停」。
🔴 **发现缺口(已作待拍板项交用户)**:`skills/video-capture/` 已是独立技能,但技能仓白名单(`.gitignore` 只放行 6 个)**不含它**
⇒ 照现状提交,仓库里的 `product-planning` 只剩一份**指向仓外路径**的交接说明,别处取仓拿不到该能力。
候选 A 加进白名单一起提交(倾向)/B 照现状提交、留已知缺口/C 压着 product-planning 那三项先不交。
---
## 🔴🔴 报障:技能包脚本把「这台机器」的路径与工作区清单写死在入库代码里(19:22 用户报,按 §G 交接整改)
**用户原话**:「session-mechanism/scripts/ 这里面的代码有问题,这个环境都是针对一台机器的,同步到别的机器把环境都搞坏了」。
**根因**:技能包**本来就有**「每台机器一份配置」的机制 —— `install.py --apply` 生成包根 `roots.env`(**不入库**,内含
`DSH_WS_ROOT` / `DSH_DOCS_ROOT` / `CODEBUDDY_CONFIG_DIR` / `COLLABD_PROD_CONFIG`),各脚本头部 `_sm_load_roots()` 以 setdefault 注入。
但下面这几处**绕开了它**,把本机路径与本机工作区清单写死在**会入库**的代码里:
- `scripts/collabctl.py`:`AIROOT = "E:/ProgramData/AIProject"`(L63)|`WORKSPACES` 四个本机工作区(L67-72)|
`BOARD_PY` 绝对路径(L75)|`KEEPALIVE_TASKS` 字典(L85-90)。
- `scripts/keepalive.py`:`PYW` 写死 pythonw 版本路径(L46)|`AIROOT`(L47)|`WS` 字典(L50-53)|`BOARD_PY`(L55)。
- `scripts/supervise-launch.py`:L28 `CODEBUDDY_CONFIG_DIR = r"E:\ProgramData\.workbuddy"`(setdefault ⇒ 兜底值本机化)。
- `scripts/board-launch.py`:L20 `DSH_WS_ROOT` 兜底写死本工作区;L29 同上一处。
**后果**:同步到别的机器 ⇒ 这些路径全不存在;跑 `collabctl.py on|ensure` 会**建出一批指向空路径的计划任务** —— 这就是"把环境搞坏"。
**修法(照既有模式,逐条)**:
1. `AIROOT` ⇒ 读 `DSH_AIROOT`(新键,落 `roots.env` 与 `.example`);兜底 = `Path(DSH_WS_ROOT).parent`。
2. `WORKSPACES` ⇒ **自动发现**:扫 `AIROOT` 下装了 `.workbuddy/collab/` 的目录(与项目看板同一套扫法)。
3. `KEEPALIVE_TASKS` ⇒ 由工作区名推导 `collabd-keepalive-<basename>`,不写死字典。
4. `BOARD_PY` ⇒ `Path(__file__).resolve().parent / "board.py"`(技能包内相对定位)。
5. `PYW` ⇒ 优先 `sys.executable`,其次宿主 env,兜底再探测。
6. 两处 `CODEBUDDY_CONFIG_DIR` 默认值 ⇒ `os.path.expanduser("~/.workbuddy")` 形态。
7. 改完跑 `selftest.py`(基线 PASS 109 / FAIL 0)+ `install.py --manifest`。
**顺带要查的**:用户说"同步到别的机器把环境都搞坏了" ⇒ 若那边**已跑过** `collabctl.py on|ensure`,会留下**指向空路径的计划任务**,需一次清理。
**属机制层** ⇒ 开工必须**独占锁**;改完跑自证。
**✅ 用户 19:28 拍板(原话)**:「不用挪出 用env文件夹区分就行,重点是 脚本是脚本 配置是配置」
⇒ 目录划分定为:技能包内建 **`env/`** 放环境专属配置,`scripts/` 只留通用代码;⛔ 不挪到宿主配置目录(我提的候选 A 被否)。
⇒ 接续任务已按此改写并重排(automation `df3dead8-37ba-4907-95c7-7896bea14383`,改写后:建 `env/`、`roots.env` 与 `.example` 迁入、
`.gitignore` 挡 `env/` 下非 `.example`、所有 `_sm_load_roots()` 与 `install.py` 的 `ROOTS_ENV` 改指 `env/roots.env`、再做去硬编码那七条)。
⚠️ 中途曾把它 PAUSED 一次(怕它按旧方案白做),改写后已恢复 ACTIVE,排期 19:32。
**✅ 19:29 / 19:30 用户再补两条口径**:
· 19:29「每个技能 需要环境配置的 都有自己的 env文件夹」⇒ 升格为**全仓包结构规范**,写进技能仓根 `改包纪律.md`;
并逐个检查其余技能(browser-harness / oil-ui-pro / product-planning / video-capture 各有 scripts;humanizer 两份是纯文档)。
· 19:30「修改完成后要仔细检查 避免调整后 各技能脚本运行失败」⇒ 验收按**实跑**算,七条:语法编译 / 全套自测 /
只读入口实跑(collabctl / board / board-launch / supervise-launch / keepalive)/ 4 个工作区常驻心跳与 20099·20100 两个看板活体 /
改动前后同命令对照 / 把 DSH_WS_ROOT 指到假目录验自适配 / 其他技能脚本同跑;**任一条不过就回滚那一项**。
任务经两次改写后重排 **19:38**。
---
## ✅ 19:56 收工:技能包「脚本是脚本、配置是配置」分层**全绿落地**(会话 `[执行]-[技能包env分层]-脚本配置分界`)
**做成了什么**
- **目录**:`session-mechanism/env/` 立起来了 —— `roots.env`(本机真身·不入库)+ `roots.env.example`(入库样例)。`install.py --apply` 现在写 `env/roots.env`。
- **去硬编码**:`collabctl.py` / `keepalive.py` / `supervise-launch.py` / `board-launch.py` 里写死的本机路径与本机工作区清单**全清**,改为 `DSH_AIROOT`(兜底 `dirname(DSH_WS_ROOT)`)+ 扫 `<AIROOT>/*/.workbuddy/collab/` **自动发现**;任务名按 `collabd-keepalive-<basename>` 推导。
- **读配置**:全包 **23 个** 脚本/hook 的 loader 统一成 `_cands` 写法 —— 向上 4 层 × (`env/roots.env` 优先 → 包根 `roots.env` 兼容),再兜底「技能库那份」`<CODEBUDDY_CONFIG_DIR>/skills/session-mechanism/env/roots.env`(工作区副本靠这条拿本机配置)。
- **`.gitignore`**(技能仓根)三层规则:`env/*` + `!env/*.example`(含 `*/` 与 `**/` 变体)⇒ 实测 `git check-ignore -v` 命中正确。
- **全仓规矩**:技能仓根 `改包纪律.md` 新增「四、包结构:脚本是脚本,配置是配置」;首个补 env 的是 `browser-harness`(`env/bh.env` + `bh.env.example`,`bh_chrome_keepalive.py` 全部默认值改 env 驱动)。
**验收读数(七条全过)**
- `py_compile`:`scripts/**` 34 个 py 全过;`bash -n preflight-lock.sh` 过。
- `selftest.py`:**PASS 109 / FAIL 0**(报告型 1 条不计)。
- **逐文件实证 loader**(AST 精确切分函数体、清空 `DSH_WS_ROOT` 后 exec):8/8 命中 `env/roots.env`;`preflight-lock.sh` 抽取片段在同目录执行 ⇒ `DOCS_ROOT`/`WS_ROOT` 均由该文件解出。
- `install.py --manifest`:**77 份、语法失败 0**(`MANIFEST_SKIP` 加 `env/roots.env` ⇒ 入库清单不随机器变)。
- 钩子冒烟:4 个改动过的 hook 空输入 rc=0;`supervise-ensure-hook` 真实载荷(cwd=ai1net)rc=0。
- 自适配:`DSH_AIROOT`/`DSH_WS_ROOT` 皆空 ⇒ 优雅报错 rc=1;指假目录 ⇒ 名单空、不崩、不建错任务。
- 活体:4 区常驻心跳读出;`:20099` / `:20100` HTTP 200。
**遗留(已登记,未动)**
- 文档库那份 `07-scripts/handoff-guard.sh`(md5 `4437d9da…`)与包内那份(`80d40d9f…`)**不同**,且它仍按位置推导根目录 ⇒ 本次只登记差异,**没改文档库那份**(发源在技能包内)。
- 3 个指向空路径的垃圾计划任务(`collabd-supervise-agent-product` / `collabd-supervise-selftest` / `dsh-board-20099`)已 `Disabled`(不删)。
- 技能仓与工作区**均未 commit/push**(用户未提要求)。
---
## ✅ 21:50 异机验证 + 提交完成(用户:「确认不同机器环境都可独立配置,脚本运行正常就可以提交」+「提交的时候 需要忽略env文件夹」)
**异机验证怎么做**:把技能包拷成「新克隆」形态(**删掉 `env/roots.env`,只留 `roots.env.example`**),
另造一台假机器的两个工作区 `ws-alpha` / `ws-beta`(`<MB>/AIProject/*`),配置目录也换成 `<MB>/conf`。
**读数(全部实测)**
- `install.py --apply --workspace <MB>/AIProject/ws-alpha` ⇒ **自生成该机的 `env/roots.env`**
(`DSH_AIROOT=<MB>/AIProject`),本机工作区名一个都没漏进去。
- `collabctl.py status` / `keepalive.py status` 在假机器上**自动发现的是 ws-alpha / ws-beta 两个**,
任务名现推为 `collabd-keepalive-ws-alpha` / `-ws-beta` ⇒ **写死的本机工作区清单确已消除**。
- **部署副本桥接**:把 `collabctl.py` 放到假工作区的 `.workbuddy/collab/`(本地无任何 `roots.env`),
只靠 `CODEBUDDY_CONFIG_DIR` 兜底 ⇒ 正确读到技能库那份 L0,认出本机 4 个区。
- `board-launch.py`:`DSH_WS_ROOT` 指不存在的目录 ⇒ 拒跑不写文件;留空 ⇒ 优雅报错不崩(⛔ 无写死兜底)。
- 整套 `selftest.py` 在假机器上:**有宿主库时 PASS 108 / FAIL 1**(唯一 FAIL =
「本区没有心跳文件 ⇒ 父链这条验不了(不假装通过)」,纯环境;本机常驻在跑时同一套是 **PASS 109 / FAIL 0**)。
**裸机器(无宿主库)**:能跑,如实报「验不了」,⛔ 不假绿。
- ⚠️ 沙箱踩坑:`selftest` **后台跑会被沙箱掐在宿主集成那几步**(输出停在副本同步、无「合计」行);
**前台跑才完整**(会用 escalation 放行)。
**提交**
- 技能仓 `E:/ProgramData/.workbuddy/skills` ⇒ **`d81f75f`**,37 文件、+762/−290。
只提本轮改动(`.gitignore` / `改包纪律.md` / `session-mechanism` / `browser-harness`);
**⛔ 没碰** `product-planning` 那笔(video-capture 独立化,属于别的任务)。
- 发现:20:27 另一个会话的提交 `90cc431` 把我那次 `git mv` 的**纯改名**顺手卷进去了(内容 0 变化)——
已核,无副作用。
- **`env/` 忽略实证**(按退出码判):`session-mechanism/env/roots.env` ⛔被忽略 /
`browser-harness/env/bh.env` ⛔被忽略 / 两个 `*.example` ✅入库 /
`*.example.bak` ⛔被忽略。头一次用「输出非空=被忽略」判错了(`check-ignore -v` 对
`!` 反选也会打行),**将来一律看退出码**:0=忽略、1=入库。
- 另补一条 `.gitignore`:`roots.env`(不带斜杠 ⇒ 按文件名匹配任意层)兜住「旧位置的包根那份」。
- 全仓 `git ls-files | grep "/env/"` ⇒ **只剩两个 `*.example`**。
**遗留**:技能仓 `master` 原先**没有上游**(`git branch -vv` 无 `[origin/master]`)。
## ✅ 21:53 用户选 A ⇒ 技能仓已配上游并推送
- 推前按技能总仓规矩核对:顶层目录 **6 个技能 + `改包纪律.md`**(白名单符合);
远端当时停在 `90cc431`,本地 `d81f75f` 领先 1 个提交 ⇒ **快进,无冲突**。
- `git push -u origin master` ⇒ `90cc431..d81f75f master -> master`,上游已设。
- 远端复核:`origin/master` = `d81f75f`(与本地同一提交);
`git ls-tree -r origin/master | grep "/env/"` ⇒ **只有两个 `*.example`**,
**没有任何 `roots.env` / `bh.env` 真身**(这轮最要紧的一条:配置没跟着上远端)。
- 工作区仓按惯例补提今日日志。