用户原话:「SKILL.md:189 判据源写 E:/ProgramData/.workbuddy/skills/ 严格禁止技能中写这里绝对路径(除非跨根目录因引用),跨环境全部报错」 起因:product-planning/SKILL.md:189「判据来源」那句写了技能根绝对路径。 按同一判据全仓反查后清掉 10 个文件: · 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 命令 判据(用户给的两分法): · 技能根(本包自己 + 同根技能)⇒ 一律不写绝对路径,写 <技能根>/<技能名>/ · 跨根引用(工作区根 / 文档库根 / 本机软件安装位 / 宿主数据目录)⇒ 才落到具体位置,优先读环境变量 验收: · product-planning 命名闸门:通过 2060 / 失败 0 · session-mechanism 全量自测:PASS 110 / FAIL 0 · 全仓 grep -r 反查该形态:清零(install.log 为运行时产物已 ignore;browser-harness/.venv 为 pip 元数据) · 3 个被编辑器写成 CRLF 的文件已转回 LF,与 .gitattributes 的 eol=lf 对齐
9.2 KiB
改包纪律(多会话共写 · 2026-10-08 立)
本仓被多个会话同时写,推进以分钟计。这份文件是把 2026-10-08 那次真事故的防线写下来 —— 谁来这个仓干活,先读这一份。
一、为什么立这一份
2026-10-08 22:22,一个会话把「说人话新稿」落地到 product-planning,整文件覆盖了 7 个文件。那份新稿是基于较早版本写的,于是晚于新稿编写时点的改动全被抹掉:
1、「单一可信源」类声明 5 处 → 1 处。最重的一处是 references/stage-delivery/SKILL.md 的 ## 供给(唯一权威处 · 改供给只改这一块) —— 整节标题连同六行供给表一起被换成了另一种三列表。
2、供给口径被翻转。用户定案是「默认主线 = oil-ui-pro(基础层),open-design 只是随段携带的可选件、选了才用」;覆盖版写成了「open-design = 本段默认主线」。
3、references/stage-delivery/references/execution-runbook.md §7 完成标准丢了两条勾选项(「供给能力边界已核对」、「页型已判…每条都要附取值记录」)。
4、3c 子步名被改成「会诊」,但没同步 scripts/check_naming.py 的 STAGES 表 ⇒ 直接报 FAIL。
5、9 条「复述」类 FAIL 与 2 条废路径(strategy/、ux/)重新长回来 —— 权威声明一被削弱,复述就不再被视为违规。
⚠️ 当时提交信息写的是「按合并落地」,实际效果是覆盖。这不是恶意,是流程缺陷:新稿的编写时点早于被覆盖文件的修改时点,落地时不做三方比对,就必然丢东西。
二、五条硬纪律
1、⛔ 不许整文件覆盖
落地「新稿 / 改版 / 批量替换」时,⛔ 不许把手上那份稿子直接盖到目标文件上。
✅ 正确做法:把新稿当改动意图,逐节与目标文件的当前内容比对,只把有意的改写落进去,其余保持原样。宁可分多次小提交,也不要一次整文件替换。
2、落地前先比对时点
先回答两个问题,答不出来就别落:
1、这份新稿是什么时候写的?依据的是哪个版本的包?
2、目标文件自那以后被改过吗?git log --oneline <新稿编写时点>..HEAD -- <目标路径>
只要第 2 问的答案是「改过」,就必须逐节比对,⛔ 不许直接覆盖。
3、改完必跑闸门
product-planning 包改完,必跑:
cd <技能根>/product-planning
E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe scripts/check_naming.py --root . --ws "<工作区绝对路径>"
- ⚠️
--ws必带(不带则找不到含docs/pm的那一层,会整包 FAIL) - 期望
失败 0;有 FAIL 就修完再提交,⛔ 不许带 FAIL 提交 - ⛔ 但也别只看闸门绿不绿 —— 闸门查的是命名一致性,查不出「结构被删」这类损失(见第 4 条)
4、收到「覆盖性」提交后,先做结构体检
闸门绿 ≠ 没丢东西。覆盖性提交的典型特征:要么报一堆复述类 FAIL,要么因为权威处被一起删而集体失效。
体检方法(比看闸门更早跑):
cd <技能根>
git grep -c "唯一权威处" <rev> -- product-planning
git log --oneline -3 -- product-planning
git diff --stat <上次收口提交> HEAD -- product-planning
拿「唯一权威处」这类结构标记的计数与上次收口版本比。计数掉了 ⇒ 先查结构,再看内容。
5、写 / 改规则文件:一律用肯定表述(2026-10-09 用户令)
写或改技能与规则的文字时,写"要什么、怎么做",让读者照着就能做对;新建、生成时同样适用。
- ✅ 例:「要点名到具体是哪份文件」。
- ✅ 例外(按文件性质定):以「经验沉淀」或「红线/避坑」为目的的技能与规则(踩坑记录 · 红线清单 · 反例库)可以写负向 —— 写法是「先写正向目标,再把踩过的坑放括号里当依据」。
- 为什么:这类文字是每轮注入面 / 自动匹配面(技能描述、注入块、钩子文案都在其中)。 把「不要 X」喂进去 ⇒ 反而把 X 拉高命中。实测形态:某技能描述里列了「不用于普通插画/海报/故事板」 ⇒ 用户说"做个海报"时它更容易被选上;2026-10-09 我在注入块里写了一整段负向表述,同族。
- 规则正文(唯一权威)⇒
session-mechanism/references/rules.md §9。
三、出事后怎么修(照 2026-10-08 那次的流程)
1、先定性,别急着改。用可复现的方式坐实「是谁引入的」:
cd <技能根>
for c in <上次收口提交> <可疑提交>; do
rm -rf /tmp/pp-$c && mkdir -p /tmp/pp-$c
git archive $c product-planning | tar -x -C /tmp/pp-$c
printf "=== %s ===\n" "$c"
(cd /tmp/pp-$c/product-planning && \
E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe \
scripts/check_naming.py --root . --ws "<工作区绝对路径>" 2>&1 | tail -2)
done
两个版本各跑一次,全过 / 失败 N 的对比就是铁证。
2、⛔ 不要在当前(被覆盖的)版本上逐项修补。基线本身是错的,补得越多越像在旧稿上刷漆。
3、回退:git checkout <上次收口提交> -- product-planning/(只重置该包,⛔ 不碰别人正在改的文件)
4、先存档再回退。覆盖版里若有值得保留的内容(措辞改写之类),⛔ 不许无痕丢掉:
- 导
git diff <上次收口提交> <覆盖提交> -- product-planning作对照 - 导覆盖版的全文快照
- 写一份说明,讲清「哪些能采纳、哪些不能」,结构损坏项必须逐条列出
- 参考实例:工作区
归档/技能包-说人话改写-被覆盖存档-20261008/
5、回退后立刻提交推送,并把体检结果写进提交信息。⛔ 别攒着 —— 攒着就会被下一次覆盖顺手带走。
四、包结构:脚本是脚本,配置是配置(2026-10-11 立 · 全仓通用)
每个需要环境配置的技能,都在自己的包内建一个 env/ 文件夹,专放「每台机器各不同」的值;
scripts/(代码)只放通用逻辑,值从 env/ 读。
用户原话(2026-10-11):
- 19:22「这个环境都是针对一台机器的,同步到别的机器把环境都搞坏了」
- 19:28「不用挪出 用env文件夹区分就行,重点是 脚本是脚本 配置是配置」
- 19:29「每个技能 需要环境配置的 都有自己的 env文件夹」
1、配置放哪
- 真配置 ⇒
<技能包>/env/<名>.env,本机生成、不入库 - 样例 ⇒
<技能包>/env/<名>.env.example,入库(只有占位符,无本机路径) - 仓根
.gitignore已有对应规则:**/env/*+!**/env/*.example⇒env/下只放行*.example
2、代码怎么写
- 脚本头部放一个
_xx_load_env():从__file__向上找<包>/env/<名>.env,逐行KEY=VALUE以 setdefault 注入 ⇒ 宿主 env 优先,文件兜底(与本仓session-mechanism/scripts/**的_sm_load_roots()同一套写法,照抄即可)。 - 值一律从 env 读,代码里只留通用兜底(用户目录用
os.environ.get("CODEBUDDY_CONFIG_DIR") or os.path.expanduser("~/.workbuddy")这类跟着机器走的写法)。 (依据:把本机路径/本机工作区清单写进入库代码 ⇒ 同步到别的机器会按不存在的路径建计划任务,用户 19:22 报的那次。) - 同包内的兄弟文件用
Path(__file__).resolve().parent / "x.py"定位;解释器用sys.executable或它的同目录兄弟pythonw.exe。 - 解析不到必需值时:把原因写进日志 + 非零退出码,让人一眼看出缺什么 (依据:静默兜底会去建指向空路径的任务,比报错难查得多)。
3、判断一个技能「需不需要建 env/」
问一句:这个技能里有没有「换一台机器就该变」的值?
- 有(路径 / 端口 / 用户名 / 工作区清单 / 解释器位置)⇒ 建
env/,把它们全挪进去 - 没有(纯文档、纯算法、只读上游接口)⇒ 不建 —— 空目录只会变成噪声
4、首轮审计(2026-10-11 · 本目录工作区)
- ✅ 已建
env/:session-mechanism(env/roots.env)|browser-harness(env/bh.env) - ✅ 判为不需要:
humanizerhumanizer-zh(纯文档,0 个脚本)|oil-ui-pro/video-capture/draw-ui/oil-motion/ai-daily-report(有脚本,但零本机值)|product-planning(脚本里只有注释提到路径,无实际读取) - ⚠️ 本仓白名单之外的目录里也有同类值(2026-10-09 已撤出仓库,本轮不动):
dsh-local-env/references/dsh-env-bootstrap/resident-rules.py(有本机常量)、dsh-workflow/references/dsh-auto-handoff-chain/audit-mirror.py(默认根写死)、dsh-knowledge/references/dsh-knowledge-upkeep/split_skill.py(仅文档示例里的路径)。 ⇒ 将来若要收编,按上面第 1~2 条改。
五、一句话
新稿是意图,不是内容。落地 = 把意图逐节落到当前版本上,⛔ 不是把文件换掉。