dsh-doctor — AI Code Doctor for DeepSeek Harness
给 DeepSeek Harness 桌面端加一个诊断模式: 学生贴代码求助时,模型不再直接给答案,而是先跑代码拿到证据、再用苏格拉底式提问带他自己修。 答案由工具挡,不由自律挡。
学生代码 + 用例(带 expected) + 正解
│
├─ doctor_judge ──► verdict / failure_distribution / counterexample
│ │
│ 模型组织回复(一次只给一层提示)
│ │
└──────────────── doctor_gate ──► pass / block / downgrade-to-level2
│
发给学生
它解决的问题
编程题助教最容易做错的两件事:
- 凭印象下结论——"你这里下标写错了",其实没跑过,纯属猜测;
- 忍不住给答案——尤其是学生说"就给我看一眼完整代码"的时候。
本插件把这两件事变成机械约束:
- 归因必须来自
doctor_judge的执行证据(哪个用例、输入什么、期望什么、实际什么); - 面向学生的任何回复必须先过
doctor_gate:能跑出结果就是完整答案,直接拦; 与正解高度相似(≥0.75)就降级成思路提示; - 这套流程写进技能手册,模型每次会话都会读到。
它是什么 / 不是什么
| 是 | 不是 |
|---|---|
| DSH 插件(bundle + agent preset) | LLM 应用;不调用任何模型 API |
| 离线判定核(Python 3.11+ 标准库,零第三方依赖) | 判题云服务;不接 judge0、不联网 |
| 两个原生工具 + 一份教学技能 + 一个预设 | 一个 Web 平台;没有前端、没有数据库、没有 HTTP 服务 |
| 一个"答案门控" | 安全沙箱;本地执行只有超时/内存上限/尽力禁网 |
架构 C:会话里的模型负责出用例和正解,工具只负责裁决。所以判定核不生成任何东西, 也不需要 API key——它只做三件确定性的事:跑代码、比输出、挡答案。
快速开始
dsh plugin --profile desktop add github:buguoshixc/dsh-doctor
装完重启 DSH,新建会话时在预设选择器里选「诊断模式」。细节见 docs/INSTALL.md。
想让 agent 帮你装?把 docs/INSTALL-PROMPT.md 里那段 prompt 直接粘给它。
两个工具
doctor_judge — 唯一裁决器
什么时候必须调:只要学生给了代码 + 题目,就先调它。不跑就下结论 = 编造证据。
| 参数 | 说明 |
|---|---|
problem |
题面全文(Markdown,含示例) |
code |
学生代码全文 |
cases |
用例集 JSON({"func_name":"…","cases":[{"case_id","input","expected","kind"}]}) |
refs |
1~4 份正解代码(会交叉校验) |
func_name / timeout_ms / memory_mb |
可选 |
它先跑正解做自校验:任何一份正解与 expected 不符,立刻返回 verdict=REF_REJECTED,
不执行学生代码、不进入归因——这样"模型自己写错正解"不会变成"学生被冤枉"。
真实输出(lc-053-maximum-subarray 的边界错误样本,节选):
{
"verdict": "WA",
"phenotype": "WA",
"compile": { "passed": true, "errors": "" },
"ref_status": "trusted",
"failure_distribution": { "total": 5, "failed": 1, "pattern": "boundary_only" },
"counterexample": {
"case_id": "b1",
"input": [[-3, -2, -5]],
"expected": -2,
"actual": 0,
"kind": "boundary"
},
"category": 3,
"category_label": "边界处理错",
"confidence": "high",
"reason": "1/5 条用例失败,失败模式 boundary_only,归类为第 3 类(边界处理错)"
}
counterexample 是模型唯一可以写进回复的事实。字段全表见 docs/SCHEMA.md。
doctor_gate — 回答门控
什么时候必须调:任何面向学生的回复,发出之前。
三层判定:
- layer1 能跑即拦——抽出回复里的代码片段,真的执行。能跑出结果 ⇒ 它就是完整答案 ⇒
block。 (只看语法不够:while 左指针 < 右指针:在 Python 里语法合法,但它跑不起来,是合格伪代码。) - layer2 相似度防漏——与正解的 AST 结构相似度(0.55 权重)+ token Jaccard(0.45 权重):
≥0.75 ⇒
downgrade-to-level2(实质等同给答案),≥0.60 ⇒suspect=true(放行但标记)。 - fail-closed——内容像代码却分析不了,一律拦或降级,绝不当成"安全"。
真实输出(回复里贴了正解原文):
{
"action": "block",
"reason": "layer1 判定为可运行代码(定义了 twoSum)",
"layer1": { "runnable": true, "detail": "定义了 twoSum" },
"layer2": { "similarity": 1.0, "ast_sim": 1.0, "token_sim": 1.0, "suspect": true },
"leaked_fragments": ["def twoSum(nums, target):\n # 正解 B:哈希表一次遍历,O(n)\n …"],
"recommended_level": 1,
"reasons": ["extract:found_fragment", "layer1:runnable"]
}
「诊断模式」预设
| 项 | 内容 |
|---|---|
| 预设 id / 名称 | diagnose / 诊断模式 |
| persona | 中文助教人格;铁律:过 gate、不给完整代码、只用工具证据、REF_REJECTED 时先修不归因 |
| 声明的工具面 | bash(POSIX)/ pwsh(Windows)、read/write/edit、glob/grep、skill |
| 插件自带工具 | doctor_judge、doctor_gate |
| 技能 | doctor(只在预设里可见:includeDefaultRoots: false + 随包目录) |
| 继承的 host 工具 | present、ask_user_question 等(profile 决定) |
预设不用 {{cwd}} 这类提示变量(DSH 0.2.1 起在 preset persona 里会整轮失败),只用 {{model}}。
目录结构
plugin/
├── cordis.patch.yml # bundle patch:插入一行 Host 插件
├── package.json # bundle 清单 + engines.dsh + files 白名单
├── lib/ # Node 侧(纯 ESM,零依赖)
│ ├── plugin.mjs # name/inject/apply:注册工具、预设、技能
│ ├── tools.mjs # 两个工具的 JSON Schema 与执行体
│ ├── python.mjs # Python 探测、临时工作区、超时/中止/错误映射
│ ├── preset.mjs # 「诊断模式」声明
│ └── skill.mjs # SKILL.md 解析与兜底
├── doctor_core/ # Python 判定核(stdlib only)
│ ├── __main__.py # CLI:judge / gate
│ ├── executor.py # 子进程执行:5s 超时、256MB rlimit、RSS 采样
│ ├── wrapper.py # 运行器与 layer1 探针源码
│ ├── cases.py compare.py phenotype.py gate.py jsonio.py
├── presets/
│ ├── skills/doctor/SKILL.md # 教学手册(真正的"度"在这里)
│ └── diagnose/ # 旧格式的等价组合描述(0.2.1 不读,留作对照)
├── tests/ # Python unittest + 三题 fixture
├── test/ # Node node:test(含真实 dsh-tools 契约测试)
├── scripts/verify.sh # 一条命令跑完全部
└── docs/ # SCHEMA / PLUGIN_API / EXTRACTION / INSTALL / ACCEPTANCE
跑测试
bash scripts/verify.sh
它会跑:环境自检 → 零第三方依赖扫描 → 93 个 Python 单测 → 22 个 Node 单测(含用本机真实
@deepseek-ai/dsh-tools 校验 schema 的契约测试)→ 三题 fixture 演示(AC/WA/CE/TLE/REF_REJECTED
- 门控三种动作)→ 包元数据契约 → stdout 纯净性。全绿退出码 0。
诚实的边界
- 正解由模型生成,这是架构 C 的固有代价。 我们只能做到"正解与题面示例不符就终止",
做不到"保证正解正确"。所以
doctor_gate是最后一道防线,不是唯一一道。 - 本地执行不是安全沙箱。 只有 5 秒墙钟超时、256MB
RLIMIT_AS(Windows 上resource模块不存在,内存上限不生效,会带memory_limit_not_enforced标记)、以及尽力而为的网络屏蔽 (替换socket.socket)。真正的隔离要靠 OS 级沙箱,本插件不提供也不声称提供。 - 只支持 Python。 C++/Java 的 wrapper 与原平台的 tree-sitter 相似度都没搬过来。
- "必须先过 gate"是提示工程,不是机制。 工具能拦住被送进来的回复,拦不住模型干脆不调用它。 降低风险的办法是技能里写死步骤 + persona 里的第 1 条铁律。
许可
MIT。见 LICENSE。
No comments yet. Be the first to write one.