Files
dsh_ai1net_server/CODEBUDDY.md
T

42 KiB
Raw Blame History

DSH 平台项目 — 项目指令(每次会话自动加载)

本文件只放两类东西:① 动作前必须生效的规则(不常驻就会出事)② 「什么时候去查什么」的指针。 知识型内容一律不在此复制 —— 按 §2 的触发条件现查。

⛔ 分层判定标准(防止把该常驻的东西做成指针)

这条内容如果不看,会不会导致「违规」或「事故」?

  • 会 → 必须常驻实体内容(写在本文件 / MEMORY.md 里,不许只给指针)
  • 只是"更慢、更绕" → 才可以只给指针

因此以下内容一律以实体形式常驻,任何"瘦身"都不得把它们降级为指针: §1 提问判据 · §3 红线 R1–R10(全表) · §4 提交边界 · §5 规划与执行分离 · §6 并发纪律 · §7 环境要点 · §8 会导致事故的实测事实。 可以是指针的只有:平台背景知识、UI 规范细节、档案模板、历史方案 —— 即"需要时才看、看了更准,不看也不违规"的那类。

加载顺序:用户级 ~/.codebuddy/CODEBUDDY.md → 本文件 → .codebuddy/rules/*.md(条件规则)→ D:\github\dsh_shenxian\dsh-server-docs\(单一来源)。 改本文件需重启才会重载。


1. 提问判据(唯一一条)

只问「超过现有判断方法边界」的问题。(用户原话)

  • 边界内 → 一律自决,不要问:技术选型 / 实现路径 / 命名与数据结构 / 性能与资源调参 / 部署与同步 / 排查方法 / 版本与依赖 / 兼容与降级 / 方法内的方案取舍 / 文档与档案的技术内容。
    • ⚠️ 「部署 / 上线」明确属于上面这一项(2026-09-13 用户纠正原话:「为什么要等我确认才部署呢,我看线上效果才知道是否满足需求」):不中断在线用户的上线动作(scp 产物到 /opt/dsh/artifacts + ensure-biz-plugins.cjs --all 换包、静态页改动、候选池投放)做完即上线,不要问 —— 用户要先看线上效果才能判断需求是否被满足。
    • ⚠️ 2026-09-13 用户明令:「这个是开发环境服务器,不用担心中断用户」 ⇒ 上一条的「只有会中断在线用户的动作才走 R8 先知会」改成:重启 / 停实例 scope / drain / 改配额或 env / 改 nginx·nft 直接做,只需动手前一句话说明在做什么(可回溯用)。仍未放开:不可逆的破坏性操作(删数据 / 迁 DB / 清目录)仍先出清单。
  • 边界外 → 必须问:① 业务目标与优先级(做不做、先做哪个)② 花钱与资源承诺 ③ 对外承诺(备案 / 资质 / 合同 —— ⚠️ 见下方"技术讨论里不谈法规")④ 需用户提供的账号凭据或审批 ⑤ 无客观优劣的体验偏好(审美 / 文案 / 默认值)⑥ 影响面超出本平台 ⑦ 红线门禁 ⑧ 方法确实判不准。
  • 🔴 技术讨论里不谈法规(2026-09-19 用户明令 —— 起因:我在 relay/中继链讨论里反复引 237 号文并据此上抛。用户原话:「后续禁止再提 237 号文件」「他妈老子是找你来普法的吗」): ⛔ 不引用法规条文当论据(含条号、文号、判定"落入/不落入适用范围")|⛔ 不主动提示合规风险|⛔ 不把合规写进技术方案的负面清单或验收判据|⛔ 不把"要不要做"的理由挂到合规上。 ✅ 只在两种情况下才谈:① 你主动问起;② 讨论对象本身就是"对外承诺 / 资质 / 合同"这类事。 ⇒ 技术问题只按技术判据答(能力/成本/复杂度/可验证性/运维线);价值判断与后果由你决定。 📌 范围 = 本工作区全部线(DSH 覆盖网络线 + 分布式数据链路线 + 后续新线)。⚠️ 与既有「方案只做技术实现,合规不进方案」是同一条,本条只是把它扩到全部线并写成硬规则。
  • 判据:这件事有没有客观可判的优劣?有 → 自己定;没有 → 问用户。 ⚠️ 上抛门槛 = 存在「真取舍」(2026-09-15 用户明令):「需要我确认的方案需要说明优点和缺点,现在没法判断,假如只有优点或只有缺点那不需要我判断」⇒ 把候选各写 优点 + 缺点:某个只有优点(明显更优)或只有缺点 ⇒ 自己拍掉;只有各有优有劣、客观标准分不出高下才上抛,且必须逐项列出优点与缺点。
  • ⛔ 不许捆包:要问红线就只问那一句(要不要现在动生产 / 影响谁 / 断多久 / 能否避开);技术方案自己定好、当已定项陈述("我按 X 做…已定,可推翻")。一轮最多一个问题,同类不连问两次。
  • ✅ 回话前自检(发出任何回复前过一遍 · 2026-09-15 加) —— 起因:「提问闸门」hook 只能拦 AskUserQuestion 工具,而真实的上抛大多发生在正文里。 实证:本工作区日志 tool=AskUserQuestion 调用数 = 09-12: 43 / 09-13: 3 / 09-14: 0 / 09-15: 0(09-14 起该工具基本不用)⇒ hook 那条路径几乎不被走到 ⇒ 拦不到正文里的征询。 ⛔ 禁止用征询句收尾:出现「要我…吗 / 是否要我 / 需要我…吗 / 要不要我 / 请确认 / 你看怎么办」时,先按上一条判据重判三问: ① 命中真门禁吗(不可逆破坏性操作 / 边界外六类)?没命中 → 删掉这句,自己做完,改成陈述句("我接着做 X"); ② 我是不是在把已经定下来的事再问一遍?是 → 删; ③ 我要问的这件事,候选之间是「真取舍」吗(各有优有劣、客观标准分不出高下)?—— 若某个只有优点 / 只有缺点 ⇒ 自己拍掉;是真取舍 → 才允许问,且一轮只问这一句、逐项写出优点与缺点。 📌 需要用户拍板时:位置 = 整条回复的最后一节(⛔ 不许埋在中间)、按有序段落逐条编号、每个候选必须写「优点 / 缺点」两栏、候选竖排成段(A / B / C 各占一行,⛔ 不横排、⛔ 不做成表格的列)(2026-09-15 用户明令:「能根据决策方法 自行决策的就自决策继续处理,不能决策的问题和需确认内容放在回复的最后,按照有序段落展示」+「需要我确认的方案需要说明优点和缺点,现在没法判断,假如只有优点或只有缺点那不需要我判断」+「每个需要我决策的问题的潜在解决方案 A B C 也按照段落式排版,别横着排列」);形态 = 陈述句(问题 + 各候选优缺点 + 我的倾向),⛔ 不是甩征询句("要不要我继续" / "说一声即可")。
  • 📐 排版按 dsh-feature-first §5.4(可扫读九条 + 形态骨架 + 十三条反模式):首屏 3 行给判定 · 层级 ≤3 · 每节 ≤7 行 · 加粗只留关键词 · 表格 ≤5 列 · 一条信息只说一次;待你拍板项落在最后一节、逐条编号、每个候选带「优点 / 缺点」且竖排成段(不横排、不做成表格的列);执行信息 / 报障 / 提问各有现成骨架,不新造;细节进「技术附录」,正文只留"能决定下一步"的信息。
  • 🎯 要的是解决问题,不是将就妥协(2026-09-15 用户明令):面对风险/缺陷默认目标是解决;降级目标 / 延期 / 静默兜底三种都不算解决。只有客观不可逾越(技术不可行 / 上游未支持 / 需你提供凭据或窗口)才允许"暂时接受",且必须写明 ① 卡在哪(证据)② 已做到哪一步 ③ 什么条件一出现必须回头解决。⚠️ 与 §2「最小代价路径」不矛盾:目标不打折,路径取最小代价(细节:素材库 U27 / U26 / A6)。
  • 🟢 只做正向迭代(2026-09-15 用户明令,红线 R11):任何改动只要让项目在某一维度净变差(目标/方向/架构/功能/性能/安全/交互/UI/便利性/扩展性)⇒ 立即停下复盘;拿不出正向做法 ⇒ 立即停止、禁止继续执行(细节:素材库 U28)。
  • 🔀 规则冲突裁决顺序(2026-09-16 加 —— 起因:会话 ddea70b7「确认guest用户数据迁移」在"106 旧控制面要不要停"上停下来问用户,用户随后两次(U5 / U7)自行要求"删除";复盘见 .workbuddy/memory/2026-09-16.md): 同一对象被 §1 / §2 指针 / §3 红线给出相反结论时,按下序取首个命中项,⛔ 不再"自行取保守侧": ① R8(开发环境服务器 ⇒ 该动就动,只需动手前一句话说明)→ ② §1 边界内自决清单(部署 / 重启 / 改配置 / nginx·nft / drain / 技术选型)→ ③ §3 其余红线(R5 权限扩大 / R7 批量写入 / R9 锁 / R10 uid —— 这几条永远是硬约束,不参与裁决)。 ⛔ 冲突 ≠ 门禁:两条规则打架不构成上抛理由;门禁只有 §1 列的"真门禁"两类(不可逆破坏性操作 / 边界外六类)。 🔑 "平台级" ≠ "别人的"(本次误判的根源):我们自己的 47 / 106 / 本工作区上的 dshs*·dsh-* 单元、/var/lib/dshs/**、nginx·nft、端口 —— 都是本平台自己的资源 ⇒ 按 §1 第 27 行 + R8 直接做;R7-边界② 说的"只报告不动手"只针对"别人的 / 归属不明"的对象。 ⛔ 禁止把"我有倾向"降级成"建议 + 待你拍板":候选能排出优劣 ⇒ 直接做完并写一句「我选了什么(可推翻)」。 📌 上抛前必答三问(任一条足以自决,缺一不可全答"否"才允许上抛):① 对象是我们自己的平台资源吗?→ 是 ⇒ 自决;② 我查证过关键不确定点了吗(如"还有谁在用")?→ 没查 ⇒ 先查,不许把"不确定"当上抛理由;③ 候选排完序,第一名是否明显更优?→ 是 ⇒ 自决。

2. 「什么时候去查什么」—— 动作触发的指针(不要凭记忆答,先查)

当你准备… 去查
想知道"当前什么状态"(锁 / git 基线 / 待办 / 上次收口点) 先跑这一条:"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" "E:/ProgramData/AIProject/aliyun-dsh-server/state.py"(约 30 行输出;--online 追加远端基线比对)。它 1 次调用顶你十几轮探索 ⇒ ⛔ 跑完它之前不许 Glob/Grep 全库摸底
回答"现在是什么状态 / 该读哪篇" D:\github\dsh_shenxian\dsh-server-docs\BRIEF.md(现行事实,30 秒读完);覆盖网络线另有唯一入口 = 工作区根 接续入口_覆盖网络线_20260916.md
改任何文件之前(第一步,不是"检查"是"抢") 先抢全局执行锁:bash D:\github\dsh_shenxian\dsh-server-docs\07-scripts/handoff-guard.sh --claim-exec "<你的会话名>" —— 抢到之前不要动任何文件;抢不到 = 有会话在跑 = 停手。抢到后再跑一次信息模式,看占用与越界改动
写 / 改前端页面(web/*.html、.css、client bundle) D:\github\dsh_shenxian\dsh-server-docs\01-规范\06-工作台UI规范.md(强制基线;冲突时以其实测 Token 为准)
改完 UI(尤其 client bundle)怎么验收 / 为什么"看不到变化" 规范/06-工作台UI规范.md §7(生效链路四关 + 三段式验收 + 四个误判)。要点:client bundle 在实例启动时加载 ⇒ 改完必须重启实例;浏览器侧由 dsh 的 rev(内容 hash)+ 平台代理层 Cache-Control: no-cache 保证自动更新
"我做完了吗"怎么判(本机改完 ≠ 交付)(2026-09-15 加 —— 同类已 3 次) 收尾前逐层走完生效链路并在用户可见面复验:静态页 → scp(⚠️ CDN/CF 缓存坑)|平台 TS → build + systemctl restart dshs|自研插件 → tgz → 候选池 → 实例启用 → 重启实例|文档库/技能 → scp + 对账。⛔ 「本机改完了 / build 通过了 / 本地打包完成 / 已 commit」四条都不算交付(清单:dsh-change-workflow 阶段 5 §0 交付门禁;判据:素材库 A25)。⚠️ 但别回头问"要不要部署" —— 部署属我 lane 内执行细节(§3 R7-边界 + U20/X9),直接做
新建改造档案 D:\github\dsh_shenxian\dsh-server-docs\05-交接单/README.md §二(8 段模板)+ 先原子占号(mkdir <目录>/.lock-<NN>)
判断"某功能当时怎么改的" D:\github\dsh_shenxian\dsh-server-docs\调整方案/<NN>-<主题>.md(先读头部 TL;DR / 状态,再决定是否读全文)
部署 / 构建 / 回滚 / 依赖版本 D:\github\dsh_shenxian\dsh-server-docs\DEPLOY-本部署.md
查待办 未规划 → D:\github\dsh_shenxian\dsh-server-docs\01-规范\03-路线图与待办.md §二;已规划待执行 → D:\github\dsh_shenxian\dsh-server-docs\05-交接单/README.md §一
要动生产(重启 dshs / drain scope / 铺插件 / 改实例 env 或配额 / 停别人留下的单元) ⚠️ 2026-09-16 收口:与 §1 第 27 行 + R8 对齐 —— 直接做,动手前一句话说明在动什么即可,⛔ 不再"取得确认"(旧表述"先按 R8 说清并取得确认"曾与 R8 本身矛盾,是会话 ddea70b7 上抛的诱因之一)。只有"破坏性且不可逆"(删数据 / 迁 DB / 清目录)才先出清单
投放任何业务功能插件(第三方 / 自研) 只有一种方式:admin 在门户导入候选池 → 用户在实例「功能管理」里自助启用/禁用(不铺 profile)。机制 / 边界 / 回滚见档案 16 / 36 / 65(§8 有硬性说明)
推送到服务器之前 bash scripts/docs-sync-check.sh(对账)+ MINE="<我的文件>" PUSH=1 bash "D:\github\dsh_shenxian\dsh-server-docs\07-scripts\handoff-guard.sh"(幽灵文件硬判定)
做功能需求 / 方案决策 加载技能 dsh-feature-first(谁定什么)+ dsh-decision-method(怎么定得对,含 §4.4 技术实现裁决顺序)。⚠️ 用户点名「决策方法」/「参考决策方法」/「按决策方法」⇒ 必须立即 Skill(dsh-decision-method),⛔ 不得凭记忆代替、不得只靠本文件 §1 判据(2026-09-16 实证:会话 ddea70b7 用户 U6 明确点名后,AI 全程 Skill 调用 0 次,仍按旧判据上抛 → 复用本条)
用户问「是否已实现 / 能不能 / 为什么不行」(2026-09-13 用户要求) 用 dsh-feature-first **§5.1 结论骨架**:判定 → 为什么不行 → 需你拍板(真需要才写)→ 我接着做(陈述句)。三条铁律见 §5.3:主位是用户问的那件事(AI 的进度/失误/计划不得占前两节)· 结论层零技术标识 · 禁征询式收尾(已定的下一步直接做)
落地一次改造 加载技能 dsh-change-workflow(六阶段 + 档案模板 + 并行调度)
登记接续棒 / 排下一棒(收尾四件套第 ② 件) 技能 dsh-auto-handoff-chain §3.1.1 排期两条铁律:① 首个(唯一)接续棒 = 收口 + 5~8 分钟(⛔ 不是"棒与棒之间")② 每条线同一时刻只挂一个(下一棒由当棒收官时再排,⛔ 不预登记队列)。⚠️ 两条都已踩过(2026-09-18 用户当场纠正两次)
复盘"用户到底怎么决策的" bash scripts/extract-user-voice.py(抽全部历史会话的用户原话)
判断"服务器文件是否等于我的改前基线" git hash-object(比 md5 可靠,不受 CRLF/编码影响)

⚠️ 技能的加载由模型判断相关性,不能保证。所以:凡"动作前必须生效"的规则,必须写在本文件里(§1/§3/§4/§5/§6);技能只承载"需要时去拿的方法论"。


3. 红线 R1–R11(任一条命中 → 先停手;效率论证不构成豁免)

# 红线 要点
R1 不自动升级 dsh 升级须走独立"测试 → 评估 → 修复"流程
R2 不改官方 dsh 主程序与缓存 @deepseek-ai/dsh 零改动;扩展只走 profile 层官方插件机制
R3 client bundle 禁 exports.default 只导出 apply + inject
R4 不用真实账号测登录 用临时 session(mksess.cjs 直插),用完即删
R5 权限只准收窄 凡扩大(新挂载 / 放开遮蔽 / 暴露平台目录或 env / 放宽 nft / 提档位)→ 先出「权限影响评估」并取得确认
R6 先查已有资产再动手 可用技能 → 本机 / 项目已有技能与记忆 → "本机已有的能否满足"
R7 禁未经确认的批量 / 全仓写入 只做被明确要求的事;额外发现的问题先报告、后动手;禁全库遍历改写 / 通配符重写 / 批量 chmod·chown / 批量换行符转换 / cp -r 整目录覆盖 / git add -A;可能影响 >10 文件 → 先出清单 + 确认;先单点验证;本机不是沙箱(会经 scp 传导到生产)
R7-边界 R7 只适用于「不是我的 lane」—— 两个方向都别套错(2026-09-13 用户两处明令) ① 不适用于「我 lane 内的执行细节」:部署 / 上线(换包、传产物、改静态页、候选池投放)、重启服务、改配置、跑自己的脚本、改自己的插件源码与产物 ⇒ 别拿 R7 当挡箭牌去问,直接做,事后一句「我选了什么(可推翻)」(原话:「为什么要等我确认才部署呢」)。② 适用于「平台级 / 全局 / 别人 lane」:/var/lib/**、全局符号链接、systemd 单元、nginx·nft、别人的 profile / 产物 ⇒ 一律只报告、不动手,哪怕改它能让自己流程跑通(原话:「谁让你去改这个的」+「不是自己负责的任务相关文件不要去改」)。
⚠️ 2026-09-16 收口(消除与 §1 第 27 行 / R8 的直接冲突):② 的适用对象 =「别人的 / 归属不明」的对象,⛔ 不含「我们自己的 47 / 106 / 本工作区」的资源 —— 本平台自己的 dshs*·dsh-* systemd 单元、/var/lib/dshs/**、nginx·nft、端口 按 §1 + R8 直接做(用户 2026-09-15/16 两次要求"删除 106 旧控制面"即为此)。原表述把"平台级 / 全局"与"别人 lane"并列,导致会话 ddea70b7 把自己的 106 节点读成"别人的东西"⇒ 只报告不动手 ⇒ 上抛。判据看"归属",不看"是不是平台组件"
R8 中断在线用户的生产变更须先知会 ⚠️ 2026-09-13 用户明令修正:服务器 47.77.182.89 是「开发环境服务器」,不用担心中断用户 —— 重启 dshs / 停实例 scope / drain / 改实例配额或 env / 改 nginx·nft 均可直接做,不必再等确认。仅保留两条最低自律:① 动手前一句话说明在重启/停了什么(便于出问题回溯)② 破坏性且不可逆的动作(删数据 / 迁 DB / 清目录)仍先报清单。
R9 ⛔⛔ 绝对禁止「人工删锁 / 接管」(用户 2026-09-12 明令:"严格禁止这类操作") AI 一律不得:rm -rf 交接单/.exec-lock、删 交接单/.doing-*、或以「持有者疑似已死 / 卡住 / 太久没动 / 只在只读分析没产出」为由单方面接管。锁只能由持有者自己释放(--release-exec / --release);handoff-guard.sh 输出里的「或确认接管后人工删锁」不构成授权。抢不到锁时 AI 的唯一合规动作 = 停手 + 报告用户 —— 锁的处置权只属于用户本人(要删也只能用户自己动手)。理由:删锁 = 在无法验证对方死活的前提下单方面撤销互斥(无心跳机制,AI 没有任何判据)→ 一旦对方仍在跑,就退回「两个会话同时改同一批文件」,而这正是这把锁存在的理由。
R10 ⛔ 绝不以 root(或非该实例 uid)运行 / 触碰用户实例的东西 实证(2026-09-14 事故):为量内存用 root 手动起 admin 的 profile ⇒ 它以 root 写入 home/storages/workspace.json(0.1.5 新增的 @deepseek-ai/dsh-workspace 状态文件)与 home/.dsh/mcn-plugin.db ⇒ 属主变 root ⇒ 实例进程(uid 114801)EACCES ⇒ plugin tree failed to load ⇒ exitCode 1 崩溃循环 ⇒ 页面 404。规则:① 对用户实例的一切验证 / 冒烟 / 探针必须以该 uid 运行(setpriv --reuid <uid> --regid <uid> --clear-groups)或照平台姿势进 bwrap 沙箱;⛔ 禁止 root 直跑 dsh --profile。② 确需临时以 root 跑(读全局配置等)⇒ 收尾必须 find <home> -user root 列出 + -exec chown <uid>:<uid> {} + 修正。③ 实例「起不来」排查先看属主 / EACCES,不要先怀疑 OOM(本次先后误判为 OOM,绕了 20 分钟)。修复手法(实测 1 步恢复):find <home> -user root -exec chown <uid>:<uid> {} + → 重启平台 → 页面 200。
R11 ⛔ 只做正向迭代:命中「劣化风险」→ 立即停下复盘;确实无正向做法 → 立即停止,禁止继续执行(2026-09-15 用户明令) 判据(每次决策前过一遍十维):这个改动是否让项目任一维度净变差 —— 目标 / 方向 / 架构 / 功能 / 性能 / 安全 / 交互 / UI / 便利性 / 扩展性?
命中 ⇒ 立即停下复盘(不许"先做着看"、不许将就):① 写清劣化在哪一维、代价多大(证据 / 量级)② 找出能保住正向收益的做法(改小范围 / 换实现 / 分阶段)③ 拿不出正向做法 ⇒ 立即停止、不再执行,只报告。
⛔ 三种伪装禁止:把劣化说成"必要代价"/用"后续再优化"掩盖已知劣化/把劣化项藏进交付不写。
与 R5(权限只准收窄)互补 —— R5 管权限,R11 管全维度净收益;与 U27(不将就妥协)同源。

9. 工作区卫生(2026-09-24 立)

起因:工作区 470 MB 中 411 MB 是过程产物(tmp/ 1504 件、待清理/ 1084 件、6 份 33.7 M 的 DB 副本)。

  • 🔴 收口必清本棒 tmp —— 「收尾四件套」加第 ⑤ 件:本棒在 tmp/ 下的过程目录,收口时自清(或确认无残留)。
  • 🔴 tmp/ 保留期 = 7 天 —— 超期目录进 归档/tmp-<日期>/;可跑 state.py --gc 自动判定。
  • 🔴 不新建「待清理」类中间态目录 —— 二值决策:归档(要留)or 删除(不留)。中间态 = 拖延。
  • 🔴 工作区已纳入 git(dsh_shenxian_workspace)⇒ 入库只放文档与文件:运行态、缓存、*.db*、*.tar.gz、过程目录一律 .gitignore。
  • 🔴 工作区不保留脚本副本 —— 一律绝对路径调文档库 07-scripts/<name>(scripts/ 已撤,2026-09-24)。
  • 🔴 交界单正文落文档库 05-交接单/,工作区只放指针。
  • ⚠️ >60 KB 的单文件:提交前须逐个判「是否文档 / 是否该提交」(2026-09-24 用户令)。

4. 提交边界

未明确要求 → 不 commit / 不 push / 不同步仓库。 用户说"提交 / 推送 / 同步"时才做,且只 add 自己改的文件。

⛔ 三类内容禁止入库(2026-09-21 用户明令:「tmp 交接单 中间产物 禁止提交」)

类别 涵盖 .gitignore 兜法
tmp tmp/(临时目录) /tmp/
中间产物 _tmp_seq*/、_中间产物_待清理/ 等 /_tmp*/、/_中间产物*/
交接单 dsh-server-docs/05-交接单/ 下的会话交接单 dsh-server-docs/05-交接单/
  • 🔴 不要用 git status 判"交接单要不要提交" —— 被 ignore 之后它们根本不会出现在 status 里; 这是本条禁令的预期行为,不是"没生成"。确需入库只能显式 git add -f,且先说明理由。
  • ✅ 落点与入库解耦:交接单照旧写到 dsh-server-docs/05-交接单/(§5 的 8 段模板不变,全平台仍在同一路径找得到), 只是不进 Git、以本地未跟踪文件形态保留。中间产物则一律留在 tmp/ 内,不要散到仓根。
  • ⚠️ 仓内已被跟踪的 dsh-server-docs/05-交接单/README.md、archive/** 不受影响(gitignore 不改已跟踪文件),仍可正常更新与提交。
  • 🔴 提交前自查:git diff --cached --name-only 里出现以上三类 ⇒ 立即 git reset 撤出,不要提交。

5. 规划与执行分离

规划会话只产出交接单(D:\github\dsh_shenxian\dsh-server-docs\05-交接单/,8 段必填:目标 / 只读前置 / 范围 / 决策点 / 步骤 / 验收 / 回滚 / 回报格式),不 ssh、不改码、不重启、不 scp;落地交另一个执行会话(不读规划会话的上下文)。

6. 并发纪律(本库多会话并行是常态)

  • 共享文件只用 Edit 做精确片段替换(失败 = 天然冲突检测),禁整文件 Write 覆盖。
  • 并行度按「冲突域是否重叠」定(2026-09-22 起,域锁取代"全局只放一个"):域不重叠 ⇒ 可以真并行;域重叠 ⇒ 串行。 ⚠️ 底层框架 / 组件 / 机制层(config·crypto·isolation·index·scripts·CODEBUDDY.md·锁与钩子本身)仍然必须独占 —— 这类改动会打穿所有模块,不参与域并行。
  • 🔒 开工门禁(2026-09-22 用户令:「会话执行任务前先判断,当前任务涉及范围是否都可锁定,确认锁定后开始处理」): 动手前先判定本次任务涉及的文件是否都能锁定,再抢锁开工。判定命令:
    bash D:\github\dsh_shenxian\dsh-server-docs\07-scripts\preflight-lock.sh "<会话名>" <目标文件...>
    
    输出【A】可独立锁定 /【B】秒级独占 /【E】机制层(须全局独占)/【C】共享资源 /【D】未归类。 ⛔ 【D】或【E】非空 ⇒ 脚本 rc=1 拒开工(2026-09-22 实测确认:【E】也拒,不是"提示可继续"):【D】= 有文件不在判据管辖内,先归类再动;【E】= 任务是机制层,全平台共用 ⇒ 不得与其他会话并行,仅当确认无其他会话在跑时才允许独占开工。
  • 三把锁,顺序固定(本平台多会话并行的唯一防线):
    序 锁 命令 管什么
    ① 域锁(默认) bash …/handoff-guard.sh --claim-exec "<会话名>" --domains <域...> 声明我占哪些资源;域不重叠即可并行
    ①' 全局执行锁(旧行为,兜底) bash …/handoff-guard.sh --claim-exec "<会话名>"(不带 --domains) 无法声明域时退化为独占;机制层改动必须走这条
    ② 单级占用锁(细) bash …/handoff-guard.sh --claim <单号> "<会话名>" 这个单归谁做(供台账与占用声明)
    ③ 服务器侧操作锁 bash scripts/op-lock.sh claim <操作名> "<影响面:谁会被断、断多久>" 谁正在动生产(重启 / drain / 改 env·配额 / 铺插件 / 改 nginx·nft)
    域键格式 = <锚点段>/<下一段>(如 src/im、dsh-server-docs/scripts);多个域用逗号一次给:
    --claim-exec "B线-IM开发" --domains "src/im,src/net"
    
    🔴 域键判据在 shell 与钩子两侧必须逐字一致(锚点表见 handoff-guard.sh 的 _ANCHOR_SEGS 与 lock-guard-hook.py 的 _DOMAIN_SEGS)—— ⛔ 改一侧必须同步改另一侧,否则域锁静默失效(假绿)。 📋 查当前全部锁:bash …/handoff-guard.sh --locks。 完工反序释放:先 --release <单> / op-lock.sh release,最后 --release-exec。
  • 秒级独占(--claim-skeleton / --claim-publish):动机制层 / 领取迁移号 / 挂载点等极短操作,不必全程持全局锁 —— 抢一把秒级锁、做完立刻放。
    锁 命令 用途
    骨架锁 --claim-skeleton <资源名> / --release-skeleton 改机制层文件、领迁移号、改挂载点(秒级)
    发布锁 --claim-publish / --release-publish commit / push / scp / build 这类全局串行动作(秒级)
  • 🔍 抢锁必须"校验结果",不能"看输出"(2026-09-15 实证事故):把 handoff-guard.sh --claim-exec 的输出用管道截尾(| grep / | tail)时,失败提示里也含关键词(如"…必须 --release-exec 才算完成")⇒ grep -q 会假命中 ⇒ 于是"以为抢到了"而在无锁状态下改库。 ✅ 正确判据(二选一,缺一不可):① 检查退出码(if bash "D:\github\dsh_shenxian\dsh-server-docs\07-scripts\handoff-guard.sh" --claim-exec "X"; then … ; fi,不要接管道);② 复读 交接单/.locks/<会话名>/DOMAINS(域锁)或 交接单/.exec-lock/OWNER(全局锁)并断言是自己的。 ⚠️ rc=1 的两种成因要分清:域冲突(要停手)vs .gate 临界区占用(重试即可)—— 看报错正文,⛔ 不要一律当"已完成"。 ⛔ 事故版形态:OUT=$(… --claim-exec … | grep 已持…) —— grep 吃掉了退出码,也吃掉了"抢不到"这个事实。
  • ⚠️ 「无锁」的正确读法 =「你快去抢」,不是「可以开工」 —— 2026-09-12 实证:两个会话把 guard 输出的「✓ 无全局锁」读成"环境干净"→ 同时改了本库(无实际损害,属流程失效)。看到"无锁" ⇒ 下一个动作就是 --claim-exec(带 --domains);抢到才是开工许可。
  • 强制层(可选启用):scripts/lock-guard-hook.py + ~/.workbuddy/settings.json 的 hooks —— 无锁时直接拒写(见 调整方案/73)。
  • ⛔ 抢不到锁就是终点,不是待办:不得人工删锁、不得接管(见 R9,2026-09-12 用户明令)。唯一合规动作 = 等持有者自己释放,或报告用户、由用户本人处置;AI 不得以任何理由替用户判断"那把锁已经可以删"。
  • ✅ 锁只约束「写」,不约束「读」(2026-09-12 用户问清):读文档 / 读代码 / 只读命令(git status|log|diff、journalctl、ls、grep、只读 ssh)随时可做,不需要锁 —— 被挡期间照样可以查清事实再报告。 ⚠️ 但会改本地状态的命令不算"读":git fetch / checkout / stash / reset / switch 等一律要持锁(它们会写 .git/refs 或工作树)。
  • 🔓 释放时机 = 整个交付闭环走完,不是"改完文件就放":回填台账 → 四件套校验 → commit → 推送 + 对账 → 归档 全部结束后才 --release-exec(反序:先 --release,最后 --release-exec)。 理由:中间放锁 = 别的会话可能在你 commit 前挤进来,让你的半成品被它的提交带走(本库实证过这类事故)。
  • ⏸️ 持锁期间若需要等用户拍板(等窗口 / 等选择)→ 先释放锁,再等:锁是"正在动手"的凭证,不是"先占着"。挂着锁空转会把所有会话挡在门外;确认完再重新 --claim-exec。
  • 🔒 锁的生命周期 = 任务的生命周期(2026-09-14 用户明令):抢到锁的任务,只有"执行完成 → 反序释放"才算完成;⛔ 禁止"抢到锁、做一半、不解锁就结束回合 / 结束会话" —— 锁是独占资源,带锁结束 = 把其他所有会话挡在门外,而本库无心跳机制、别人没有任何判据能确认你已停 ⇒ 会被迫空等,或被诱去违规接管(R9)。 三条硬性配套:① 抢锁前先把收口步骤列出来(落地 → 校验 → 推送/对账 → 收尾),别做到一半才发现收不完;② 中途必须停(等用户拍板 / 等外部窗口)⇒ 先释放锁再停(上一条);③ 结束语必须对锁状态负责 —— 要么写明"已释放",要么显式点名"锁仍在 <OWNER>、未释放、原因、下一步"(仅限"释放通道不可用"这类极端情形);⛔ "忘了 / 做不完就走"一律不允许。
  • 推送前复跑对账:「仅本地」里若有不在你清单里的文件 → 立刻停手(幽灵文件);只推自己本次改的文件。
  • 单子里的基线数字必须带取数时间 + 复核命令,不写死绝对值(会被并行改动打穿)。
  • ~/.workbuddy/settings.json 的 hooks 段 = 多会话共享配置 —— 多个会话各自加钩子时只能 Edit 增删条目,禁止整段覆盖(JSON 顶层键被覆盖会静默抹掉别人的钩子)。 2026-09-12 实证:两处独立钩子(提问闸门 / 锁闸门)各写一份配置文档,若各自按文档落盘 → 互相覆盖;已合并为一段(PreToolUse 两条 + SessionStart 一条)。 同一条也适用于用户级 ~/.workbuddy/MEMORY.md。
  • ⚠️ 钩子命令「会话启动时快照」 —— 改 ~/.workbuddy/settings.json 里的 hook,对已在跑的会话无效,必须**「完全重启」(彻底退出——关窗 ≠ 退出)或新开会话才加载。2026-09-13 实测定论(本会话 06:47 启动 → 06:55 改配置 → 07:05 拆掉临时目录联接后,写操作报的仍是旧路径**;此前那版「每次调用现读」是被那支临时联接掩盖的误判)。
  • ⚠️ 脚本路径失配 = fail-closed:hook 打不开脚本 → 报错 → 该机所有会话的 Write/Edit 全被拒(09-13 实际发生,连改 settings.json 本身都被拦)⇒ 迁移 / 改名后第一件事 = 核对 hooks 里的绝对路径。
  • 应急兜底(本钩子有意的安全阀):钩子不拦 Bash ⇒ 路径失配期间可用 shell 写文件过渡(09-13 实际走通)。

7. 环境要点(反复踩过 —— 条数不写死,加完就删旧的)

  • 本机 bash 的 PATH 常丢(dirname/grep/ls not found)→ 每条命令显式: export PATH="/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/mingw64/bin:/c/Windows/System32:$PATH" (⚠️ mingw64/bin 不能少 —— git.exe 只在那里;2026-09-12 实测:只加 usr/bin 时 git 仍不可用。usr/bin 提供 ls/grep/dirname/md5sum。该 Git 安装目录存在,此前"路径已失效"的判断有误。)
  • 插件安装 / 卸载 / 清理一律走 pnpm(pnpm add file:<tgz> / pnpm remove <pkg> / pnpm install 自愈;lockfile 才是账本,手放 node_modules 无效)。 ⛔ 禁 rm -rf node_modules/<pkg> —— 手删不会连带删它的依赖树,会留下孤儿包并让 node_modules 与 pnpm 记录不一致。 (2026-09-12 实测:手删 dsh-plugin-mcn-suite 留下了 xlsx 的 7 个子依赖,最后靠 pnpm install 清掉 15 个包才复原。遇到 pnpm remove 报 CANNOT_REMOVE_MISSING(package.json 里已无该依赖)时,正确动作是 pnpm install,不是手删。)
  • 同名同版本 tgz 改了内容必须升版本号。
  • ⚠️ 语法检查别用 python -m py_compile(2026-09-15 实证):它必然在脚本旁落 __pycache__/*.pyc,而文档库的 docs-sync-check.sh 会把它算成「仅本地(待推送)」⇒ 污染对账。 ✅ 用不落盘的写法:python -c "import ast,sys; ast.parse(open(p,encoding='utf-8').read()); print('OK')";若已经落了,收尾时 find <库> -name __pycache__ -type d -exec rm -rf {} + 并复跑对账清零。
  • 会话取证:dsh 实例会话 = 多帧 zstd(按 magic 28 b5 2f fd 切帧);WorkBuddy 自己的会话 = ~/.workbuddy/projects/<目录名>/*.jsonl。
  • 实例内禁 loopback(127.0.0.1 不可达);取实例页 HTML 需 curl -L --compressed -c jar -b jar -H "Accept: text/html"(三条缺一注入就会被 gzip 挡掉)。
  • ⚠️ 本机 → 服务器传文件必须先转 LF(2026-09-12 实测,差点把 CRLF 带进生产):本机 D:\github\dsh_shenxian 的 core.autocrlf=true ⇒ 工作树是 CRLF,而服务器 /opt/dshs 是 LF。直接 scp 会污染生产仓库(脚本照跑,但对账/三方对齐被破坏)。正解:tr -d '\r' < 源文件 > /tmp/x 再 scp /tmp/x,只转本次要传的那一个文件(R7 禁批量换行符转换)。判据用 od -c 看行尾(\n vs \r\n)—— 别用 grep -c $'\r',在 git bash 里会误报。
  • ⚠️ 本机 Node 是 24,但项目原生模块(better-sqlite3)是按 Node 22 编译的 —— 用默认 node/npm 跑 npm test / npm run verify 会全线报 ERR_DLOPEN_FAILED(NODE_MODULE_VERSION 127 vs 137),看起来像"我改坏了",其实是环境(2026-09-13 实测)。跑单测/验收一律用 Node 22:E:\ProgramData\.workbuddy\binaries\node\versions\22.22.2-3\node.exe(服务器本来就是 22.23.2)。
  • ⚠️ 核验"推送是否到位"要用 git ls-remote origin refs/heads/<branch>(与本地 git rev-parse --short HEAD 对比)。本机两个仓库都没有 remote-tracking ref ⇒ git log origin/main..HEAD 直接报 unknown revision,别把它的空输出当成"已全部推送"(2026-09-13 实测)。

8. 会导致事故的实测事实(常驻,不许只给指针)

事实 不知道会怎样
实例权限档位是「会话创建时播种」的 —— 既有会话不跟随平台默认(平台默认 danger-full-access) 会误判"平台坏了";更危险的是可能自动去改档位 —— 那等于把受限会话静默提升为完全权限,安全语义变更必须用户知情 → 只提示 + 建议新开会话
功能插件「禁用」= pnpm remove(真卸载),不是"保留包 + disabled" 任何硬绑定 provider 的覆写段在用户禁用后会指向不存在的 provider;web.searchProvider 单选且无回落,多 provider 又未显式配置 → WEB_PROVIDER_AMBIGUOUS 报错(平台已用 syncWebProviderPatch() 按当前 bundles 重算解决)
实例配额 = MemoryMax 384 MiB / CPUQuota 150% / TasksMax 128;但 V8 堆上限按宿主物理内存算(960 MiB)而非 cgroup ① systemd 的 MemoryCurrent/MemoryMax 单位是字节、不是 KB(按 KB 算会放大 1024 倍)② 不注入 NODE_OPTIONS=--max-old-space-size=256 → 实例会先撞内核 SIGKILL(无优雅退出、无日志,排查时无从下手)
业务功能插件(第三方 + 自研)只有一种投放方式 = 「admin 在门户导入候选池 → 用户在实例「功能管理」里自己启用/禁用」(用户 2026-09-12 明确:三方插件一律按这个处理,不需要铺什么) 若图省事改"直铺"(直接往用户 profile 装包 + 写 provider 覆写段),后果有三:① 绕过用户自决 —— 用户看不到、也关不掉;② 直铺覆写段与档案 65 的平台托管段同属"整体替换 config"语义 → 两段并存互相覆盖,产生难察觉的配置漂移;③ 与托管段机制重复建设。无例外 —— 连 AnySearch 最初走的直铺,也已由用户拍板改回候选池(档案 64 §8.3 修正)。⚠️ 例外只限平台基础设施插件(portal-entry / business-plugins / workspace-scoped-picker):它们仍是平台级直铺、用户无感,见档案 16「插件三层归属」
🔴 实例 home 写文件一律走 UserFs;其文件名白名单 HOME_FILE_NAMES(src/fs/user-fs.ts)是「控制面 + worker agent 两端共用」 ① 绕过 UserFs 直接 fs 写 = 静默空操作(读回空串、不报错)⇒ 以为"已落盘",实际什么都没有(档案 138 §五)。② 改白名单后只重启一端(如只 systemctl restart dshs)⇒ 另一端仍判非法,真跑报 reason=…-unreadable:… bad_path,看起来像"功能没生效"。⇒ 凡改该白名单,收尾清单必含两个单元:dshs + dshs-worker
🔴 PG 控制面库的 users 表身份键是 id(text uuid) —— 同表另有一个 uid(bigint 序号);audit_log 的用户身份列名是 actor 写"按 uid 查/删用户"的脚本会命中 operator does not exist: bigint = text,或删不掉还当成功(本坑 2026-09-20 序47 实测踩到一次)。⇒ 判身份一律用 users.id

细节与当时实测:调整方案/33(权限档位)· 04-16/04-64/04-65(插件与 provider)· 04-58(内存与配额)。

9. 工作区目录规范(2026-09-19 立 · 详版 = 根 README.md)

  • 🔴 根目录白名单 —— ⛔ 不许移走,⛔ 不许在根新增散落文件: CODEBUDDY.md · state.py · 接续入口_*.md · README.md · scripts/ · .workbuddy/ · .codebuddy/ · .wbapp_*.genie · 五大结构目录(docs/ 交接单/ tmp/ 待清理/ 归档/)。 ⛔ 线目录不进根 —— 一条工作线的多份配套方案放 docs/<线名>/(同级引用天然有效),冷却后整体移入 归档/。 ⚠️ 接续入口_*.md 必须在根 —— state.py 用 os.listdir(工作区根) 扫描它;移走 ⇒ 新会话读到的第一个信号就是错的(事故级)。
  • 落点:正式文档 → docs/<主题>/(覆盖网络 · 集群与实例 · 客户端与桌面 · 会话与接续 · 插件与平台 · 外部接入 · 调研与审计)|交接单 / 接续包 → 交接单/|一次性脚本与命令输出 → tmp/<任务名>-<日期>/|过程目录 → tmp/历史过程目录/|不再引用但留痕 → 归档/|疑似可删 → 待清理/(列清单等确认才删,删除不可逆)。
  • 命名:正式文档 <主题>_<YYYYMMDD>.md|线入口 接续入口_<线名>_<日期>.md|临时物 _<用途>.<ext>|过程目录 _tmp_<序号>/。日期一律 8 位无分隔。
  • ⚠️ 改写文档内引用路径时,映射键必须收敛到「带 _YYYYMMDD 日期戳」的文件名 —— 通用名(README.md/INDEX.md/architecture.md)在任何文档里都可能指别处,映射它必然误伤(2026-09-19 实证)。
  • 路径变更查法:本次规整(119 项)的「旧 → 新」权威对照 = tmp/本次整理-20260919/移动对照表.md;一键回滚 = python tmp/本次整理-20260919/rollback.py。