deepseek-harness 个人集成分支维护技能全解析:dsh-customize / dsh-upgrade / dsh-upstream-customization 与原子化启动器切换机制
【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness
DeepSeek Harness(dsh)把个人定制从"装一次、改一处"升级为可复现、可串行、可回滚的工程流程:仓库通过dsh-customize、dsh-upgrade、dsh-upstream-customization三个技能,让个人在集成分支(staging)上隔离任务、合入上游变更、并向官方上游发布成果。本文以归档的流程决策笔记 2026-07-23-personal-staging-maintenance-skills.md 为骨架,结合当前仓库的 skill 子系统源码与文档,深入讲解这套"克隆 → 验证 → 原子切换 → 回滚保留"的维护模型,读完你可以复现其设计,并理解为什么它优于在活跃检出上原地变基等替代方案。
背景:个人定制维护为什么需要一套流程
dsh 的定位是"一切皆插件",个人用户往往会针对自己的安装做大量定制:改插件行为、加私有功能、调整界面。这些定制天然面临四个重复出现的问题:
- 定位已安装的源码:运行中的 dsh 到底从哪个检出(checkout)加载,不能靠猜;
- 隔离任务变更:多项任务如果混在同一个工作区,互相污染、难以发布;
- 串行集成:多路定制并发修改同一集成分支,会让准备中的升级历史失效;
- 合入上游变更:定制积压后,需要在不破坏"正在运行的会话所依赖的检出"的前提下,安全地把上游更新合进来。
笔记明确指出:"用户本地指令"只能解决单套安装的问题——它无法指导其他用户,也无法与仓库分发的安装脚本行为保持同步。一旦安装脚本的行为变了,本地指令就会悄悄漂移失效。因此,决策是把这套维护流程以技能的形式随仓库分发。
说明:该笔记状态为
implemented,于 2026-08-10 归档。按 .agents/notes/README.md 的归档策略,归档内容永久冻结、不作为当前行为的权威依据。从当前检出与仓库历史(如cleanup: remove TUI package and legacy dsh entrypoints、cleanup: remove managed source installer等提交)看,根级skills/目录与 TUI 已不在当前仓库中;下文按笔记原始记录还原其设计与原理。
决策概览:三个技能的分工与分发
仓库从根skills/目录分发三个技能,职责严格分离:
| 技能 | 职责 |
|---|---|
dsh-customize | 在个人安装上定位源码、用任务 worktree 隔离变更、串行集成定制 |
dsh-upgrade | 定位当前生效检出与集成分支,把上游变更合入,同时不破坏运行中的会话 |
dsh-upstream-customization | 独立于本地维护,把经筛选的定制推荐、发布给上游 |
几个贯穿始终的设计原则:
- 描述即入口:每个技能的 description 同时说明"操作内容"和"会选中它的用户请求",让模型与用户都能按意图命中正确的技能;
- 以安装的启动器为准:工作流根据"已安装的启动器(launcher)"推导当前生效的检出和集成分支,而不是依赖个人路径或分支名——路径和分支名会变,启动器是唯一稳定锚点;
- 遵从仓库内指令:流程优先遵循仓库自身分发的本地指令,避免与安装脚本行为脱节;
- 强制任务 worktree:每一项任务的变更都在独立 worktree 中进行;
- 串行化集成分支修改:利用集成分支所在 worktree 既有的
.agents/merge.lock,让每一次个人集成分支修改排队执行,避免写写冲突。
技能发现优先级:仓库级技能放在哪一层
笔记提到,分发的 TUI 在启动时把根skills/目录交给本地技能提供方(skill provider),并且该目录在发现优先级上位于项目根与用户根之后。当前仓库的 skill 子系统文档 docs/subsystems/skills.md 保留了完整的本地发现优先级表,可与之互相印证:
| 优先级 | 来源 | 路径 |
|---|---|---|
| 100 | project-dsh | <projectRoot>/.dsh/skills |
| 200 | project-agents | <projectRoot>/.agents/skills |
| 300 | custom | Config.customSkillDirs |
| 400 | user-dsh | <dshHome>/skills |
| 500 | user-agents | <agentsHome>/skills |
| 600 | bundled | Config.bundledSkillDirwhen configured |
其中bundled(rank 600)排在 project 与 user 各层之后,与笔记中"低于项目根和用户根的发现优先级"一致。这意味着:用户在工作区放置同名技能时可以覆盖仓库级默认,而仓库级技能只作为兜底分发——既保证"开箱即有安全规则",又不剥夺用户覆盖的自由。技能注册表在 packages/skill/skill 中实现:多个 provider 的目录会被合并,按 rank、provider 顺序再按本地顺序消解同名冲突。
dsh-upgrade 升级流程:克隆、验证、原子切换
升级是整个维护模型里最复杂、也最体现工程取舍的部分,可拆成四个阶段。
阶段一:变基前的 Git 检查
在真正执行变基之前,升级流程先检查Git 日志与提交范围,目标是识别四类信息:
- 将进入升级的上游变更(incoming upstream changes);
- 个人提交(personal commits),即本地定制的来源;
- 重复内容:上游已经通过官方渠道合入的定制;
- 可能冲突的区域,提前定位冲突面。
基于检查结果,流程会做一次"去重裁剪":丢弃上游已经提供的定制;如果某个定制在本地只剩"文档差异"(说明文档与上游不同,但代码已被上游吸收),也一并丢弃该说明——除非该说明包含上游缺失、且可独立使用的当前约定(current contract)。这个例外保证:上游代码虽已同步,但本地积累的有价值的使用约定不会因去重而丢失。
阶段二:单一时间戳贯穿整次尝试
每次升级尝试都使用同一个 UTC 基本格式时间戳(<timestamp>)来命名这次尝试的全部产物,形成一一对应的命名族:
| 产物 | 命名 |
|---|---|
| 独立同级克隆 | dsh-staging-<timestamp> |
| 本地准备分支 | dsh-upgrade/prepare-<timestamp> |
| 新的集成分支 | dsh-staging/<timestamp> |
| 私有上游引用与恢复引用 | 含<timestamp>的私有 refs |
| 启动器备份 | 本次尝试专用的备份 |
两个值得注意的约束:
- 同级克隆的名称不派生自当前目录名。目录名可能含非法字符、过长或撞名,而时间戳唯一且稳定;
- 名称冲突直接失败,而不是追加临时后缀。
dsh-staging-<timestamp>若已存在,说明上一次尝试残留或并发尝试存在,此时让流程失败比"悄悄换个名字继续"安全得多——后缀只会掩盖问题、制造难以追踪的孤儿产物。
阶段三:推导进程源码位置,仓库视为不可变
升级流程推导"当前 DSH 进程的源码位置"时,依据的是进程命令行与运行时环境(process command 和 runtime environment),而不是 shell 工作目录——shell 的 cwd 随时可变,而进程实际加载的安装位置才是事实。推导出安装位置后:
- 把已安装启动器所指向的仓库和检出视为不可变;
- 唯一例外是"持有其既有的合并锁"(即前面提到的
.agents/merge.lock)。
也就是说,升级绝不向运行中的检出写入任何内容,只借用它的锁来保证串行。
阶段四:验证 → 建分支 → 原子切换启动器
升级路径不是"在旧检出上原地变基",而是"换一个全新的检出让启动器指过去":
- 在独立克隆中完成依赖安装、检查与验证;
- 创建并验证带时间戳的集成分支
dsh-staging/<timestamp>; - 原子地(atomic)把启动器从"保持不变的旧集成分支检出"一次性切换到新的集成分支检出;
- 切换后要求重启一次dsh。
这条路径的关键不变量是:启动器绝不会指向准备、功能、评审、发布或分离状态的检出——它只会指向已验证的集成分支检出。切换的失败处理分两侧:
- 切换前失败:已安装的检出与启动器保持原样,没有任何残留影响;
- 切换后失败:恢复启动器备份,并验证备份确实可用(restore + verify),而非假设恢复成功。
旧集成分支检出、其分支、恢复引用和启动器备份会一直保留,充当回滚存储,直到满足两个条件才允许清理:重启后的进程证明 DSH 确实运行在新的集成分支上,且用户明确批准回滚清理。也就是说,回滚窗口由"运行证据 + 用户确认"双重把关,而不是拍脑袋定时删除。
dsh-upstream-customization:向上游发布的独立流程
本地维护与上游发布被刻意拆成两条独立流程。dsh-upstream-customization负责后者,它有清晰的推荐边界:
- 推荐上游化的变更类型:bug 修复、附加式且不与现有功能冲突的插件功能、视觉改进;
- 需要先取得维护者批准的变更类型:侵入式变更。
发布的决策权始终保留在用户手里,流程设计成"多级确认":
- 升级结束时,agent 对剩余定制分类、说明每项的上游价值、给出是否推荐的建议;
- agent 询问用户希望上游化哪个具名候选(named candidate);
- 只有用户做出选择后,才加载发布工作流;
- 每项功能在推送或创建草稿 PR 之前仍必须得到明确批准——即"推荐"不等于"自动推送"。
发布本身的质量门槛也很明确:
- 获批的变更以当前上游
master为起点,不带入任何无关的个人提交——保证 PR 的 diff 干净、可评审; - TUI 功能的草稿 PR建议附带截图,且截图取自"组装后的应用",并在截取前移除凭证与个人数据,避免泄露敏感信息。
dsh-customize:定制集成的落地约束
dsh-customize负责个人定制侧的落地,核心约束是在集成前必须于专用 tmux 会话中检验交互式 TUI 行为。原因很直接:TUI 是交互程序,脱离真实终端环境(伪终端、尺寸、按键流)无法可靠验证;tmux 会话提供了稳定的交互终端,让 agent 能真实驱动并观察界面反馈。这与此前仓库的浏览器/终端演示类技能(如 record-browser-gif)的"真实环境取证"思路一脉相承。
备选方案与取舍
笔记记录了几个被否定的替代方案,理解它们能更清楚为什么最终方案"重"但正确:
| 备选方案 | 被否定的原因 |
|---|---|
| 工作流仅限用户本地 | 其他用户无法发现同一套安全规则;工作流会与仓库分发的安装脚本行为漂移 |
| 在当前集成分支检出上原地变基 | 准备期间会修改大量文件,可能干扰新的 dsh 启动;无法提供原子发布或一份保持不变的、可回滚的检出 |
| 先把启动器迁往别处,再更新现有检出 | 升级中途启动器会指向"非集成分支"的目标;仍会改写可能承载运行中进程的检出 |
| 只在最终切换分支时加锁 | 持锁时间更短,但变基准备期间其他写入方仍可基于旧基线合并定制,导致已准备好的历史失效 |
| 用一个上游 PR 发布所有个人变更 | 减少分支管理,却会把无关定制一起发布,并取消"按功能逐项批准"的用户边界 |
最终方案的本质权衡是:用一次克隆 + 一次原子切换 + 一次重启,换取运行中检出绝对不可变、切换可回滚、上游发布可逐项授权。
影响与保障机制
升级准备流程在安装依赖和运行检查期间持有已安装集成分支的合并锁,因此本地定制的集成必须等待一个一致的结果——这是串行化的直接代价,也是正确性前提。整体保障体系可以归纳为:
- 单次升级的资源预算:一个独立的时间戳克隆、一个集成分支、一次原子启动器切换、一次重启;除此之外,升级绝不写入启动器背后的仓库或检出(仅持有既有锁);
- 防御性执行纪律:记录前置条件 → 在每次修改前重复检查前置条件 → 修改被中断后检查状态→ 切换失败时恢复并验证启动器备份 → 修正后重跑失败项→ 报告最终状态;
- 回滚存储:旧集成分支检出持续保留,直到"新分支运行证据 + 用户批准"双重条件满足;
- 仓库内评估覆盖:skill 选择、进程源码保护、不安全的仓库状态、回滚、发布授权均有 check-in 的评估用例;
- 文档门禁:仓库文档检查验证技能链接与格式(归档笔记则按 .agents/notes/README.md 的冻结策略被文档门禁跳过);
- 分工边界:Git 与文件系统操作的正确性仍由技术评审负责,而非由自动化全权兜底。
小结:这套模型的通用启示
把这份决策笔记抽象出来,它回答的是任何"带运行中的进程 + 可升级的安装"类工具都会遇到的问题:如何安全地把新版本"换"进去,而不是"改"进去。dsh 的答案——进程来源以启动器为准、变更发生在独立克隆、切换用单时间戳命名族保证可追踪、回滚由运行证据与用户批准双重把关、上游发布走逐项授权——是一套可以迁移到其他项目"安装/升级/定制/上流"四段式维护场景的成熟模板。若需进一步了解技能注册表与 provider 机制的实现,可继续阅读 packages/skill/skill/README.md 与 docs/subsystems/skills.md。
【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考