ThunderForge 开发文档
August 25, 2026 · View on GitHub
给开发者(维护者/贡献者)的第一天读物。讲清为什么这么设计、怎么改、怎么验、怎么发、哪些红线不能碰。 用户向说明见
README.md;分阶段计划见PRD.md;发布清单见RELEASE.md;网络坑手册见NETWORK-NOTES.md。
1. 项目是什么
ThunderForge(dsh-thunderforge)是 DeepSeek Harness(DSH)的一站式插件开发套件,形态是单一 bundle:dsh plugin add 一次,获得「创建 → 开发 → 调试 → 环境验证」的完整闭环。包含五个插件 + 四层知识库:
| 模块 | 路径 | 职责 |
|---|---|---|
| thunderforge-capture | src/capture/ | LLM 载荷捕获(清洁室实现) |
| thunderforge-skills | src/skills/ + skills/ | 四层知识库注册与内容 |
| thunderforge-scaffold | src/scaffold/ | 对话式脚手架生成器 |
| thunderforge-debugger | src/debugger/ | 双数据源轨迹瀑布 |
| thunderforge-profile | src/profile/ | profile 管理 + dev preset |
2. 关键架构决策(改代码前必读)
2.1 单一 bundle,插件按子路径引用
package.json 的 dsh.bundle.patch 指向 cordis.patch.yml,patch 里按包名子路径引用插件行:
- insert:
- id: thunderforge-capture
name: dsh-thunderforge/capture
新增插件 = 新增 src/<模块>/index.js + 在 cordis.patch.yml 加一行 + package.json exports 加子路径。
2.2 零 harness 运行时依赖(铁律)
规则:禁止 import ... from '@deepseek-ai/...'(src/ 下受控文件零导入)。这是踩过真崩溃换来的:
- 曾把
@deepseek-ai/dsh-tools声明为普通依赖 → pnpm 往 profile 装了第二份副本 TOOL_RUNTIME_SCHEDULER是Symbol()(内容寻址,每次模块加载新建)→ 两份副本符号不等 →ctx.tools[scheduler]为 undefined → 多工具并发调用 turn 崩溃(Cannot read properties of undefined (reading 'prepare'))- 修复:所有 util 自己实现;
dsh-tools/schemastery降为 optional peerDependencies(纯元数据)
推论:工具注册不能用 defineTool(那是 dsh-tools 的导出),必须用原始 JSON Schema 注册:
ctx.tools.register({
name: 'my_tool',
description: '...',
parameters: { type: 'object', properties: {...}, required: [...], additionalProperties: false },
output: { schema: { type: 'object', additionalProperties: true }, render: (_a, v) => [{ type: 'text', text: JSON.stringify(v) }] },
async execute(args) { ... }, // 需自行校验输入
})
真机契约(ctx.tools.register 实际校验,见 test/tool-contract.test.mjs):
output必须声明{ schema, render }(缺了直接 TypeError)schema.type只能取object/array/string/number/integer/boolean/null——'json'是 defineTool 专用糖,raw 不认- 显式对象节点必须声明
additionalProperties: boolean - schema 里不要出现 DSL 风格的
required: true属性键(未知关键字会被拒,必填用顶层required: [...]数组) parameters建议是完整 object schema(执行期校验安全)
2.3 层序策略(capture 生效的关键)
llm-deepseek 适配器行在 @deepseek-ai/dsh-base 层内部。capture 靠包装 llm.registerAdapter 工作,若 thunderforge 排在 base 之后,适配器早已注册、包装落空(llm 服务无公开枚举手段,迟到无法补救)。
规则:dsh-thunderforge 必须位于 profile dsh.profile.bundles 数组最前(先于 @deepseek-ai/dsh-base)。响应式注入会让 capture 恰好在 llm 服务出现后、适配器行之前挂上补丁。
thunderforge_profile create-dev-preset生成时已自动排好- 装进既有 profile 需手动把 bundle 挪到最前(README 有提示)
2.4 清洁室与 vendor 原则
src/capture/是清洁室实现:只依据官方 LLM 适配器协议编写,未使用无许可证上游dsh-payload-capture的任何代码(该组件被明确排除,见LICENSES/README.md)- vendor 引入(
src/debugger/session-log.js来自 dsh-replay、skills/arch-standard、skills/pitfalls、skills/dsh-buddy):未修改实现,仅文件头前置来源声明;许可证原文进LICENSES/。dshp 的 profile 实现已吸收并入src/profile/store.js+src/profile/portable-format.js(MIT 归属保留于文件头) - 红线:不引入/不参照无许可证(All Rights Reserved)的代码
3. 本地开发环境
node >= 22.19 # node -v 验证
# dsh CLI(验证 profile 层加载用):
# 推荐全局安装:npm i -g @deepseek-ai/dsh@0.1.1-rc.2
# 或复用现有:$USERPROFILE/.dsh/profiles/node_modules/@deepseek-ai/dsh/lib/bin.js
项目自身零安装依赖(node --test 即可跑测试,无需 npm i——这是零依赖设计的红利)。
4. 代码地图
src/
├── capture/
│ ├── core.js # 清洗/聚合/存储/轮转纯逻辑(零依赖,可单测)
│ └── index.js # 插件入口:包装 llm.registerAdapter(透明代理 + dispose 还原)
├── skills/
│ └── index.js # 注册四层技能(ctx.skills.register + 目录 resourceBase)
├── scaffold/
│ ├── templates.js # 三类骨架模板(tool/events/webui)+ 埋点清单
│ └── index.js # thunderforge_scaffold 工具:生成 → 落盘 → 冒烟
├── debugger/
│ ├── session-log.js # vendor: dsh-replay 引擎(zstd 容器 + chunk-row 解码)
│ ├── align.js # 双数据源对齐(session 事件 × capture index.jsonl)
│ └── index.js # thunderforge_debugger 工具(sessions/summary/waterfall)
└── profile/
├── store.js # profile 读写(dshp 实现已吸收并入,见文件头沿革)
├── portable-format.js # 可移植 profile 序列化(同上沿革)
└── index.js # thunderforge_profile 工具(list/export/create-dev-preset/verify)
skills/ # 四层知识库内容
├── thunderforge-dev/ # 入口索引(自研)
├── arch-standard/ # vendor: dsh-plugin-dev-skills(MIT)
├── pitfalls/ # vendor: dsh-plugin-guide(Apache-2.0)
└── dsh-buddy/ # vendor: oneinitAI/dsh-buddy(MIT,同作者仓库)
scripts/
├── github-push.mjs # 抗网络推送(git 优先 + API 降级 + 血统校验)
├── release.mjs # 一键发布(bump→测试→npm→推送→registry 验证)
└── smoke-capture.mjs # capture 端到端冒烟(假适配器全链路)
test/ # node --test(node:test,无框架)
├── capture.test.mjs # 清洗/聚合/存储/轮转
├── skills.test.mjs # 技能注册/开关/词典与画像条款
├── scaffold.test.mjs # 三模板生成即冒烟 + 错误值
├── debugger.test.mjs # 双源对齐 + 真实会话验证
├── profile.test.mjs # preset 生成/防覆盖/patch 数组契约
└── tool-contract.test.mjs # 真机工具注册契约(必读 §2.2)
docs/ # PRD / RELEASE / NETWORK-NOTES / DEVELOPMENT / notes/
5. 开发流程
# 1. 改代码。遵循 §2 的决策(零依赖、原始 JSON Schema、层序、合规)
# 2. 跑测试
node --test # 全量(当前 31 项)
node --test test/<模块>.test.mjs # 单模块
# 3. 真机验证(mock 测不出的错必须真机把关)
dsh --profile <某profile> --dump-config # 层加载 + 无 boot 报错
# 新增/改动工具时先过一遍 test/tool-contract.test.mjs 的规则
# 4. 提交(遵循仓库内的 Conventional Commits 风格,中文 message 亦可)
git add -A && git commit -m "feat(xxx): 描述"
# 5. 推送
git push # 网络正常时
node scripts/github-push.mjs --trust-remote # 443 被掐 / 历史形状差异时
6. 测试哲学
- mock 单测负责逻辑:清洗规则、聚合、轮转、错误路径、模板内容
- 契约测试负责真机规则:
tool-contract.test.mjs把ctx.tools.register的真实校验固化成断言——mock 测不出"register 拒绝什么",契约测能 - 冒烟负责全链路:
smoke-capture.mjs用假适配器驱动完整 注册→流→落盘→dispose 链路 - 真机 boot 是最后一道门:
--dump-config无报错 + 层加载齐全。历史上最贵的两个坑(Symbol 双实例、raw 注册契约)都是真机先抓出来的——不信 mock,只信真机
7. 发布流程
node scripts/release.mjs patch # 一键:测试→bump→npm→推送→registry 验证
node scripts/release.mjs patch --dry-run # 只看计划
注意:
- npm 有 2FA:
npm publish需 OTP,脚本自动检测登录态,未登录/OTP 时明确提示剩余手动步骤 - Windows 下脚本内部用
npm.cmd经 shell 解析(曾踩 spawn ENOENT) - 发布后用户侧:
dsh plugin --profile <名> update dsh-thunderforge+ 重启对应应用 - CHANGELOG.md 每个版本都要记(含"git only 未单独发布"的版本,注明并入下一版)
8. 网络与推送
详见 NETWORK-NOTES.md。要点:
github.com:443被掐但api.github.com通 →node scripts/github-push.mjs自动降级 API 通道(单提交、blob sha 复用、删除同步)- API 推送后本地/远端历史形状可能不同(内容一致)→ 网络恢复后
git fetch origin && git reset origin/main --trust-remote:远端提交不在本地历史时做血统校验(tree sha 是否在 reflog 中);--allow-divergent:已人工验证时显式跳过- Node fetch 遇本地代理 TLS 拦截 → 自动带
--use-system-ca重启自身
9. 技能写作规范(Skill Authoring)
权威依据:Agent Skills 官方规范与官方指南:
- Specification(frontmatter 字段、命名规则、渐进式披露)
- Optimizing skill descriptions(触发评测方法论)
本项目的两个自研技能(skills/thunderforge-dev、skills/dsh-buddy,独立仓库 dsh-buddy 同源)按此规范重写(v0.3.0)。新技能必须遵守以下规则:
9.1 frontmatter 规范
name:kebab-case(^[a-z0-9]+(?:-[a-z0-9]+)*$),≤64 字符,与目录同名,禁止大写/连字符结尾/连续连字符description:** imperative 动词开头**("Use when..."),同时写清「做什么 + 何时用 + 何时不用(边界)」,≤1024 字符,单行(本项目 frontmatter 解析器按单行读取)- 触发优化三原则(官方指南):以用户意图为中心(不是内部机制)、宁可偏 pushy(显式列出隐式触发场景——用户没点名的也算)、边界写清("Not for..." 防误触发)
metadata:author/version 必填,可加 sources 记录方法论出处
9.2 正文与渐进式披露
- 主
SKILL.md目标 < 500 行;详细资料(API 参考、完整规则集)放references/按需加载——模型只在需要时拉取 - 正文结构建议:任务触发后第一件事 = 决策表/判断流程(本项目都是「索引型」技能,先决定查哪一层)
evals/trigger-queries.json强制存在:约 20 条查询,正例覆盖直接点名/隐式描述/多步工作流,负例一半无关领域、一半易混淆近邻(近邻比无关更有验证价值)- 训练/验证集划分(60/40 防过拟合):
train_queries用于定位失败、指导改 description;validation_queries只在确认泛化时使用,禁止用验证集结果进修改过程 - 改 description 后:train 集指导修改(正例漏触发→过窄,补场景;负例误触→过宽,写边界),不抄失败查询的关键词(找它代表的类别);每次修改把通过率记进提交信息(上游惯例:
evals: train 11/11, validation 8/8)
9.3 本项目落地记录
thunderforge-dev:description 重写为 "Use when developing, reviewing, debugging...",正例含隐式("给 agent 加一个能读文件的工具"不提 dsh)、负例含近邻(Koishi / Claude Code hooks / Chrome 扩展 / 通用编程)dsh-buddy:description 重写为 "Use when communicating with a user about DSH, plugins, code...",负例含专业流畅(无校准信号不触发)- 两个技能的触发评测集均已按 60/40 拆为 train/validation 并更新 methodology 字段
10. 贡献指南(简版)
- 先读本文件,尤其 §2 决策与红线
- 改动尽量小、可回滚;每个版本补 CHANGELOG
- 新 vendor 任何上游 → 协议确认(宽松协议才可引入)+
LICENSES/台账 + 文件头来源声明 - 新工具 → 过真机契约规则 + 补
tool-contract.test.mjs覆盖 - 提交信息说明「为什么」(决策与教训),不只是「改了什么」
- 提 PR 前:
node --test全绿 + 真机 dump-config 无报错
10. 已知边界与待办
- live 端到端(真模型对话 → skill 触发 → capture 落盘 → waterfall 对齐)已于 2026-08-24 真机验证通过(见 HANDOFF §十)
- live skill 触发评测:train 6/6、validation 4/4(web 真机抽样,结果记入
skills/thunderforge-dev/evals/trigger-queries.jsonmethodology);注意 skill 触发要求 agent preset 含 Skills(minimal 极简模式裁剪 skill 工具) - 改进与拓展路线(骨架 upgrade 器、llm-adapter 模板、MCP 双端暴露、capture 协议告警等)→ 见
ROADMAP.md