dsh-jvm-doctor
Manifest validJVM production-report doctor for DeepSeek Harness: jstack/cmd thread dumps, GC logs, Maven/Gradle dependency conflicts, stack traces and Spring config diffs parsed into structured diagnoses.
dsh-jvm-doctor
离线、确定性的 JVM 生产报告诊断器 — 一个 DeepSeek Harness 插件,向模型注册 5 个工具:粘贴线程 dump / GC 日志 / 依赖树 / 异常栈 / Spring 配置文本,换回结构化诊断与诚实的解析覆盖率回执。
MIT License · DSH 0.2.0-rc.2 验证通过 · Node ≥ 20.10(运行)/ ≥ 22.6(测试)
永不接触运行中的进程 · 无
jcmd/jstack/jmap/jstat/ attach / JMX · 无网络 · 无文件读取 · 无时钟、无随机 · 每次调用都带字节回执
快速开始
dsh plugin --profile web add dsh-jvm-doctor
装完重启应用。想一次装齐四个插件(含 deterministic-tools / py-doctor / win-native)用 dsh-toolbox-bundle;别和单独安装混用。
家族总览:zicode.com/software 是四个插件、合装包与桌面软件的统一入口。
15 秒看效果 —— 把异常栈贴进去,模型调用 java_stack_trace,拿回(实测输出):
root_cause: java.lang.IllegalStateException: inner
primary: java.lang.RuntimeException: boom
frames: com.acme.Main.main(Main.java:7) coverage: 100% (3/3, parser 1)
线程 dump 同理:java_thread_dump 给出 Java 级死锁环(waiting_for / holding 链),而不是让你在 500 个线程里自己找。
为什么不让模型自己写 pwsh? 死锁环、GC 停顿 p50/p90/p99、依赖冲突消解、... N more 折叠还原——写成脚本都是几十行,每次还得重新写对;工具是固定实现 + 47 个测试,并且只出结构化事实、不做因果推断。
相邻插件:dsh-deterministic-tools · dsh-py-doctor · dsh-win-native · dsh-toolbox-bundle
为什么需要它
JVM 生产排查的证据几乎全部长这样:一段 jstack 输出、一坨 -Xlog:gc 日志、一棵 dependency:tree、一条 Caused by: 链、一份 application.yml。这些文本运维手里已经有了,但模型拿到之后只能:
- 自己起进程去采 —— 生产环境通常不允许:容器里没有
jcmd、权限与审计不让 attach、事故期间更不该再给 JVM 加负担; - 或者肉眼心算 —— 95 行 jstack 里哪几个线程互相持有锁、
Found 1 deadlock之外还有没有第二个环、omitted for conflict with到底谁赢了,全靠猜,而且不可复现。
dsh-jvm-doctor 只做一件事:把已到手的文本变成结构化诊断。它刻意不做采集 —— 没有 jcmd、没有 jstack、没有 attach、没有 JMX、没有网络、没有文件系统读取。每个工具都是纯函数:同样的输入永远给同样的输出。
顺带解决一个更隐蔽的问题:「解析失败」和「没有异常」长得一模一样。所有工具都返回 unknown_lines / unknown_samples / coverage / confidence,并且把预算导致的截断逐条报数,所以「没发现问题」和「没看懂输入」永远不会被混为一谈。
设计契约:每个结果都是回执
规范返回值固定为:
{
"result": { "…工具自己的载荷…": true },
"meta": {
"bytesIn": 5396,
"bytesOut": 7716,
"truncated": false,
"notes": []
}
}
模型看到的就是 result(对象给格式化 JSON),只有当发生截断或需要解释时才追加页脚:
[bounded] input=2307B output=5615B
[note] 7 frame(s) hidden behind `... N more` could not be resolved because the enclosing trace was truncated or absent
干净的调用不花额外 token;有问题的调用一定说清楚。
边界行为(五种,绝不混淆)
| 情况 | 行为 |
|---|---|
输入超 maxInputBytes | 抛错拒绝:报出实际字节、上限,并明确「没有任何截断或部分处理」 |
渲染文本超 maxOutputBytes | 按 code point 边界截断 + truncated: true + note(meta.bytesOut 仍报真实载荷大小) |
| 结构化列表超预算(线程数 / GC cause 数 / 冲突数 / 每条冲突的边数 / 异常数 / 每异常的帧数 / 差异条数) | 截断并逐条报数:threads_omitted、causes_omitted、conflicts_omitted、edges_omitted、exceptions_omitted、frames_omitted、changes_omitted |
| 不认识的语法 | 绝不静默丢弃:计入 unknown_lines,unknown_samples 里给出原文,并按覆盖率下调 confidence |
| 无法给出结论 | 返回诚实标记(confidence、findings、errors[]),而不是编一个诊断 |
安装
npm(推荐):
dsh plugin --profile web add dsh-jvm-doctor
预构建 tarball(与 npm 上是同一份产物,无需构建步骤,也避开 pnpm 的 allowBuilds 授权):
dsh plugin --profile web add https://github.com/ax2/dsh-jvm-doctor/releases/latest/download/dsh-jvm-doctor.tgz
要固定版本就把 latest/download/dsh-jvm-doctor.tgz 换成 download/v<version>/dsh-jvm-doctor-<version>.tgz。
本地开发:不安装,直接挂载构建产物。
pnpm install && pnpm build
dsh --profile web --patch ./cordis.local-overlay.yml --dump-config # 先验证层合成
dsh --profile web --patch ./cordis.local-overlay.yml # 再启动
配置
apply(ctx, config) 接受以下配置(全部可选,含默认值):
| 字段 | 默认 | 作用 |
|---|---|---|
maxInputBytes | 2097152 | 单份粘贴文本的硬上限(UTF-8 字节),超过即拒绝 |
maxOutputBytes | 262144 | 渲染文本的软上限,超过即按 code point 截断并标记 |
maxThreads | 200 | java_thread_dump 序列化的线程条目上限 |
maxStatusLines | 40 | java_thread_dump 汇总的状态行上限 |
maxContention | 20 | java_thread_dump 报告的锁竞争队列上限 |
maxGcEvents | 500 | java_gc_log 保留的 GC cause 种类上限 |
maxPauseSamples | 5000 | 计算百分位所用的停顿样本上限 |
maxConflicts | 100 | java_dependency_conflicts 报告的冲突 artifact 上限 |
maxEdgesPerConflict | 20 | 每条冲突保留的边上限 |
maxExceptions | 25 | java_stack_trace 保留的异常链条目上限 |
maxFramesPerException | 60 | 每个异常保留的帧上限 |
maxChanges | 500 | spring_config_diff 报告的差异条数上限 |
maxKeys | 5000 | 每份配置文档展平后的键上限 |
sampleLimit | 5 | unknown_samples 保留的原文行数上限 |
toolPrefix | '' | 工具名前缀,例如 jvm → jvm__java_thread_dump |
only | [] | 只注册指定工具(空 = 全部) |
非法值(0、负数、NaN、Infinity)静默回落到默认值,并向下取整 —— 一个坏配置无法把预算关掉。
工具清单(5 个)
| 工具 | 说明 |
|---|---|
java_thread_dump | 解析 jstack / jcmd <pid> Thread.print:线程普查、归一化与原文状态直方图、daemon 数、每线程 top 帧与锁、Java 级死锁环(含 waiting_for/holding 链)、Object.wait/park 计数、锁竞争队列 |
java_gc_log | 解析 JEP 158 统一日志与 JDK 9 之前的旧式 GC 行:事件/停顿计数、总/均/最大停顿、p50/p90/p99 百分位、Young/Full 拆分、堆前后区间、cause 直方图、promotion failure,并在有时间戳时估算分配速率 |
java_dependency_conflicts | 解析 Maven dependency:tree -Dverbose 与 Gradle dependencies / dependencyInsight:同一 artifact 的多版本、omitted for conflict with X、omitted for duplicate、每条边所在深度、逐 artifact 结论摘要 |
java_stack_trace | 解析异常链:类型与消息、class.method(file:line) 帧、Caused by: 链、Suppressed: 块、... N more 折叠按外层 trace 还原、最深的业务帧、最多 6 条可能原因提示 |
spring_config_diff | 把两份 application.yml / .properties 展平成点号键后逐键对比(add / remove / change);敏感值在进入载荷之前就替换为 *** |
java_thread_dump — 死锁环与竞争队列
输入(节选;真实示例是完整 jstack -l):
Full thread dump OpenJDK 64-Bit Server VM (21.0.4+7-LTS-103 mixed mode, sharing):
"deadlock-A" #40 ... nid=0x2b00 waiting for monitor entry
java.lang.Thread.State: BLOCKED (on object monitor)
at com.acme.order.Inventory.reserve(Inventory.java:61)
- waiting to lock <0x0000000712d00020> (a com.acme.order.Inventory)
- locked <0x0000000712c00010> (a com.acme.order.OrderDao)
...
Found one Java-level deadlock:
"deadlock-A":
waiting to lock monitor 0x... (object 0x0000000712d00020, a com.acme.order.Inventory),
which is held by "deadlock-B"
"Thead-B": ... which is held by "deadlock-A"
Found 1 deadlock.
输出(节选,… 表示省略):
{
"source": "jstack",
"jvm_version": "OpenJDK 64-Bit Server VM",
"captured_at": "2026-01-02 03:04:05",
"thread_count": 9,
"daemon_threads": 2,
"state_histogram": { "RUNNABLE": 1, "WAITING": 1, "BLOCKED": 5, "TIMED_WAITING": 2 },
"raw_state_histogram": {
"RUNNABLE": 1, "WAITING (parking)": 1, "BLOCKED (on object monitor)": 5,
"TIMED_WAITING (sleeping)": 1, "TIMED_WAITING (on object monitor)": 1
},
"deadlock_marker": true,
"reported_deadlock_count": 1,
"deadlock_cycles": [{
"threads": ["deadlock-A", "deadlock-B"],
"locks": ["0x0000000712d00020", "0x0000000712c00010"],
"chain": [
{ "thread": "deadlock-A", "waiting_for": "0x0000000712d00020", "holding": "0x0000000712c00010" },
{ "thread": "deadlock-B", "waiting_for": "0x0000000712c00010", "holding": "0x0000000712d00020" }
]
}],
"lock_contention_candidates": [{
"method": "com.acme.order.OrderDao.save",
"thread_count": 4,
"threads": ["worker-1", "worker-2", "worker-3", "deadlock-B"],
"states": ["BLOCKED"]
}],
"total_lines": 95, "parsed_lines": 95, "unknown_lines": 0,
"coverage": 1, "confidence": "high"
}
state_histogram 是归一化枚举(BLOCKED),raw_state_histogram 保留 JVM 原话(BLOCKED (on object monitor))—— 归一化便于统计,原文便于核对,两者都不丢。
明确拒绝:文本超 maxInputBytes 抛错;只认得 dump 语法的行,任何其它行都进 unknown_lines 并降 confidence,不会当成「线程正常」。
java_gc_log — 停顿统计与分配速率
[2026-01-02T03:04:05.678+0800][info][gc] Using G1
[2026-01-02T03:04:05.678+0800][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 128M->45M(512M) 12.345ms
[2026-01-02T03:04:07.100+0800][info][gc] GC(2) Pause Young (Concurrent Start) (Metadata GC Threshold) 480M->120M(512M) 30.500ms
[2026-01-02T03:04:08.100+0800][info][gc] GC(4) Pause Full (G1 Compaction Pause) 500M->90M(512M) 200.000ms
{
"gc_event_count": 6, "pause_count": 5, "non_gc_lines": 1,
"total_pause_ms": 268.095, "mean_pause_ms": 53.619, "max_pause_ms": 200,
"pause_percentiles": { "p50_ms": 20, "p90_ms": 132.2, "p99_ms": 193.22, "max_ms": 200 },
"young_pause_count": 4, "full_pause_count": 1, "concurrent_cycle_count": 1,
"heap_before_max_bytes": 524288000, "heap_after_min_bytes": 41943040, "heap_total_max_bytes": 536870912,
"unique_causes": 4, "causes_omitted": 0,
"cause_histogram": [
{ "reason": "Pause Young (Normal) (G1 Evacuation Pause)", "count": 3, "mean_pause_ms": 12.532, "max_pause_ms": 20 },
{ "reason": "Pause Full (G1 Compaction Pause)", "count": 1, "mean_pause_ms": 200, "max_pause_ms": 200 }
],
"estimated_allocation_rate": { "bytes_per_second": 31182870, "bytes_per_hour": 112258331344, "window_seconds": 2.522, "events_with_timestamp": 6 },
"findings": [
"1 pause(s) triggered by Metadata GC Threshold: class-loader or metaspace pressure is forcing collections",
"1 full-collection pause(s) present; check old-generation occupancy and humongous allocations",
"heap occupancy reached 97.7% before a collection: collections are running close to exhaustion"
],
"coverage": 1, "confidence": "high"
}
堆大小一律换算成字节(128M → 134217728),百分位用最近秩法在保留的样本上计算;样本被 maxPauseSamples 截断时 pause_percentiles_sampled: true 并附 note。同时认得旧式 [GC (Allocation Failure) [PSYoungGen: ...] 1234K->567K(2048K), 0.0123456 secs] 与 [Times: user=... real=... secs],并在 pause_source 里说明停顿秒数取自内联字段、尾随 secs] 还是 [Times: real=]。
java_dependency_conflicts — 谁被裁掉了
[INFO] com.acme:order-service:jar:1.0.0
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.18:compile
[INFO] | \- org.springframework:spring-web:jar:5.3.31:compile
[INFO] +- com.acme:widget-core:jar:1.2.3:compile
[INFO] | \- com.google.guava:guava:jar:31.1-jre:compile
[INFO] +- com.acme:widget-extras:jar:2.0.0:compile
[INFO] | \- com.google.guava:guava:jar:32.1.3-jre:compile (omitted for conflict with 31.1-jre)
{
"source": "maven", "edge_count": 9, "artifact_count": 8,
"omitted_edge_count": 2, "duplicate_markers": 1,
"conflicts": [{
"artifact": "com.google.guava:guava",
"kind": "version_conflict",
"resolved_versions": ["31.1-jre"],
"omitted_versions": ["32.1.3-jre"],
"depths": [1], "min_depth": 1, "max_depth": 1,
"edges_omitted": 0,
"summary": "com.google.guava:guava is requested at 31.1-jre; the build resolved 31.1-jre; 1 edge(s) pruned at depth 1..1"
}],
"coverage": 1, "confidence": "high"
}
Maven 的 +-/\- 与 Gradle 的 +---/\--- 各自成树,-> 记入 requested_version:请求版本与被解析版本分开存,因为「构建实际用哪个」和「谁想要哪个」是两个问题。treeDepthOf 让 Maven 与 Gradle 的深度定义一致(顶层直接依赖 = 深度 0)。
java_stack_trace — ... N more 不是黑盒
org.springframework.web.util.NestedServletException: Request processing failed; …
at com.acme.web.CatalogController.list(CatalogController.java:64)
Caused by: java.lang.NullPointerException: Cannot invoke "…Product.getSku()" because "product" is null
at com.acme.catalog.CatalogService.toSku(CatalogService.java:112)
... 15 more
{
"exception_count": 2,
"root_cause": "java.lang.NullPointerException",
"chain": [
{ "type": "org.springframework.web.util.NestedServletException", "relation": "primary", "frame_count": 3,
"deepest_application_frame": "com.acme.web.CatalogController.list",
"hints": ["NestedServletException surfaced through com.acme.web.CatalogController.list; …"] },
{ "type": "java.lang.NullPointerException", "relation": "caused_by", "frame_count": 16, "folded_frames": 15,
"folded_resolved": 3, "deepest_application_frame": "com.acme.catalog.CatalogService.toSku",
"hints": ["NullPointerException dereferences a null receiver or return value at com.acme.catalog.CatalogService.toSku; …"] }
],
"total_frames": 7, "folded_frames_resolved": 3, "folded_frames_unresolved": 12,
"exceptions_omitted": 0, "frames_omitted": 0,
"confidence": "medium"
}
折叠帧只从最近的外层异常里还原(Java 的语义如此),还原不出来就计入 folded_frames_unresolved 并降 confidence —— 不编造不存在的帧。application_frames、library_prefixes、test_failure 用来区分业务边界与框架管道。
spring_config_diff — 密钥不落地
{
"left_dialect": "yaml", "right_dialect": "yaml",
"left_keys": 8, "right_keys": 8,
"added": 1, "removed": 1, "changed": 4, "unchanged": 3,
"masked_values": 2,
"changes": [
{ "key": "spring.datasource.hikari.connection-timeout", "op": "add", "left": null, "right": "3000", "masked": false },
{ "key": "server.shutdown", "op": "remove", "left": "immediate", "right": null, "masked": false },
{ "key": "logging.level.root", "op": "change", "left": "INFO", "right": "WARN", "masked": false },
{ "key": "server.port", "op": "change", "left": "8080", "right": "9090", "masked": false },
{ "key": "spring.datasource.hikari.maximum-pool-size", "op": "change", "left": "10", "right": "32", "masked": false },
{ "key": "spring.datasource.url", "op": "change", "left": "jdbc:postgresql://db:5432/orders", "right": "jdbc:postgresql://db-primary:5432/orders", "masked": false }
],
"errors": [],
"findings": ["2 secret-looking value(s) were replaced with *** before comparison"]
}
键名命中 password|secret|token|key|credential 时,左右两侧的值在进入载荷之前就替换为 ***(masked: true 仍然说明这里有一处变化),所以密码既不会被比较、也不会被回显。.properties 的转义(\: \= \\ \ )与行内注释照常解析;解析不了的行进 errors[] 并带上行号,调用本身仍然是成功结果。
诚实性设计:coverage 与 confidence
所有解析器共用同一套记账(src/core/lines.ts):
total_lines按 CR/LF 归一化后计数,单个结尾换行不额外造一行;- 空行算结构、不算语法:把空行计入
unknown_lines会让覆盖率取决于报告排版多松散; coverage = (total - unknown) / total;confidence:coverage ≥ 0.98且没有可疑信号 →high;≥ 0.85→medium;否则low;- 「可疑信号」指文档解析成功但没有任何主体(0 个线程 / 0 条依赖边),这种情况永远拿不到
high; - 完全空的输入是
low,不是「100% 覆盖率」的假象; unknown_samples只保留前sampleLimit行原文,且永远不丢计数。
离线与安全边界
- 不采集:没有
jcmd/jstack/jmap/jstat/jinfo、没有 attach API、没有 JMX、没有 socket、没有子进程; - 不读盘:报告文本只能从工具入参进来;
- 不联网:没有任何出站请求,包括遥测;
- 不猜密码:
spring_config_diff在比较之前就把敏感值掩码; - 可复现:无时钟、无随机、无环境变量读取、无 locale 依赖;同一份文本永远给同一份结果(含
bytesIn/bytesOut)。
开发
pnpm install
pnpm typecheck # tsc --noEmit,0 错误
pnpm test # 47 个用例(5 个工具,用例数 ≥ 工具数)
pnpm build # 产出 lib/(ESM + .d.ts,导入已改写为 .js)
测试分三类:
- 解析器金值测试(
test/core.test.ts,27 例):内联真实的jstack/ 两种 GC 日志 / Maven 与 Gradle 依赖树 / 多层异常链 / YAML 与 properties fixture,断言精确的结构化数字(线程数、直方图、死锁环的waiting_for/holding、百分位、堆字节数、冲突深度),而不是「非空」; - 组装路径测试(
test/tools.test.ts,20 例):definition.execute(args, exec)→definition.output.render(),断言result/meta/ 页脚,以及每个工具的 schema 形状与「离线 + 拒绝」描述;only/toolPrefix过滤、resolveLimits非法值回落、maxOutputBytes截断路径; - 诚实性测试:故意塞入被截断/被人类改过的 fixture,断言覆盖率、
unknown_samples、confidence如实地变差(例如 100 行里 4 行不认识 →coverage 0.96、confidence medium),并断言每个预算截断都报出精确的丢弃条数(exceptions_omitted、causes_omitted、edges_omitted…)。
兼容性与风险
- 官方插件 API 自称 pre-stable,无冻结承诺;本插件只依赖最稳定的两个面:
ctx.tools.register(defineTool(...))与apply(ctx, config)。 - 验证到什么程度(不夸大):
tsc --noEmit0 错误;47 个用例在真实@deepseek-ai/dsh-tools上跑通组装路径;lib/产物由research/plugin-smoke.mjs(stub registry)加载并验证 5 个工具全部注册、schema 齐全、无重名、无 default export、dsh.bundle指向存在且 id 正确的 patch;Node 22 / 24 的 CI 矩阵;npm 上的版本用dsh plugin --profile <scratch> add装进一个临时 profile 后,真实dsh启动把本包 5 个工具 schema 发给了模型(会话日志核对)。真实对话也跑通了:同一个临时 profile 接上本机 desktop profile 用的那条 LLM 路由后,模型在真实回合里调用了java_stack_trace(3 行异常链),正确报出root_cause、帧位置与 100% 解析覆盖率(会话session-b5681b93)。尚未做:跨多个真实会话的调用统计(这属于 M3)。 - 语法是启发式的:不同 JDK 版本的 dump/GC 文案、各构建工具的输出变体都可能落在已知语法之外 —— 这正是
unknown_lines存在的理由;覆盖率下降就是「这份报告我没完全看懂」的信号,请以它为准。 - 不做因果推断:工具给的是结构化事实与明确的 finding(例如「97.7% 堆占用触发回收」),不是「你的服务就是这样挂的」。
- GC 分配速率是线性估算(首尾时间戳窗口内的正堆增量),不含分代细节,仅作量级参考。
Roadmap
- JDK 版本感知:为 JDK 8 / 11 / 17 / 21 的 dump 与 GC 文案分别固定 golden 语料,把版本差异变成测试而不是猜测。
- 更多报告:
jcmd GC.heap_info文本、JFR 摘要、线程 dump 的差集对比(两次 dump 之间谁新增/谁消失)。 - 建议引擎:把
findings从「事实陈述」升级为可核对的检查项(例如「持有锁超过 N 个采样」),并保持每个结论可回溯到具体行号。 output.presentCall/presentResult卡片化,让 Web UI 直接渲染线程表与死锁环图,而不是纯文本 JSON。
License
MIT © ax2
English
Offline, deterministic JVM production-report diagnostics for DeepSeek Harness — 5 model-visible tools that turn pasted jstack / GC logs / dependency trees / exception chains / Spring configuration into structured diagnostics with an honest parse-coverage receipt.
- Never touches a running process. No
jcmd,jstack,jmap,jstat, no attach API, no JMX, no sockets, no subprocesses, no filesystem reads. Every tool is a pure function of the text you paste: no clock, no randomness, no environment reads, so the same input always yields the same output. - Every call returns a receipt:
{ result, meta: { bytesIn, bytesOut, truncated, notes } }. Model-facing text is exactlyresult; a footer appears only when something was clipped or needs explaining. - Explicit failure, never silent degradation. Over-budget input is refused with both sizes and an assurance that nothing was partially processed. Rendered text is clipped at code-point boundaries and flagged. Every budget that drops list entries reports the exact count (
threads_omitted,causes_omitted,conflicts_omitted,edges_omitted,exceptions_omitted,frames_omitted,changes_omitted). - Unrecognized syntax is never dropped: it is counted in
unknown_lines, sampled verbatim inunknown_samples, and lowersconfidence. An entirely empty paste islowconfidence, not a fake 100% coverage. - Secrets stay out of the payload:
spring_config_diffreplaces values whose key matchespassword|secret|token|key|credentialwith***before comparison.
Tools:
| Tool | What it answers |
|---|---|
java_thread_dump | Thread census, normalized and raw state histograms, daemon count, per-thread top frames and locks, Java-level deadlock cycles with the waiting_for/holding chain, Object.wait/park counts, lock-contention cohorts |
java_gc_log | JEP 158 unified and legacy GC grammar: event/pause counts, total/mean/max pause, p50/p90/p99 percentiles, Young vs Full split, heap before/after ranges in bytes, cause histogram, promotion failures, allocation-rate estimate |
java_dependency_conflicts | Maven dependency:tree -Dverbose and Gradle dependencies/dependencyInsight: same artifact at several versions, omitted for conflict with X, omitted for duplicate, per-edge depth, per-artifact summary |
java_stack_trace | Exception chain: type and message, class.method(file:line) frames, Caused by: chain, Suppressed: blocks, ... N more folds resolved against the enclosing trace, deepest application frame, up to six likely-cause hints |
spring_config_diff | Flatten two application.yml / .properties documents to dotted keys and classify every difference as add, remove or change — with sensitive values masked before they reach the payload |
- Install:
dsh plugin --profile web add dsh-jvm-doctor, or mount the built entry locally withdsh --profile web --patch ./cordis.local-overlay.yml. - Develop:
pnpm install && pnpm typecheck && pnpm test && pnpm build— 47 tests (parser golden values + assembled tool path + honesty/degradation cases) against the real@deepseek-ai/dsh-tools. - Verified against DSH 0.2.0-rc.2 on Node 22 and 24; only
ctx.tools.register(defineTool(...))andapply(ctx, config)are used, because the official plugin API is explicitly pre-stable. - Honest about grammar: dump and GC wording varies across JDK versions and build tools. Coverage below
1is the signal that part of the report was not understood — treatunknown_linesandconfidenceas part of the answer, not as noise.
Comments
Loading…
Similar plugins
by MkaliezZ
Structured stack-trace parsing for DSH: Python, Node.js, Rust, and Java error frames with bounded metadata.
★ 0
MIT
TypeScript
Aug 29, 2026
dsh plugin --profile web add @mkaliezz/dsh-error-explainerby JohnXu22786
Structured machine-readable CI output for DeepSeek Harness: JSON/NDJSON session reports, JUnit XML, semantic exit codes, artifact collection and privacy redaction - a dsh profile bundle plus a standal
★ 1
MIT
TypeScript
Oct 16, 2026
dsh plugin --profile web add dsh-headless-jsonby ax2
One command installs four deterministic DeepSeek Harness plugins: 46 offline, bounded tools for JSON/YAML/CSV/encoding chores plus JVM, Python and Windows production diagnostics.
★ 0
MIT
JavaScript
Oct 13, 2026
dsh plugin --profile web add dsh-toolbox-bundleby 1149784810
General file previewer for DeepSeek Harness (dsh): vertical expandable produced-files list with per-type rendering — markdown, syntax-highlighted code, csv/tsv tables, images, text.
★ 0
MIT
TypeScript
Aug 15, 2026
dsh plugin --profile web add jayhe-mdviewerby YYTbit
Jev-scored context compaction for DeepSeek Harness. Score every message by relevance to the current task instead of dropping the oldest, pin the tracebacks and constraints, and fall back to stock beha
★ 1
MIT
TypeScript
Oct 3, 2026
dsh plugin --profile web add dsh-plugin-jev-compactionDiagnostics for DeepSeek Harness plugins: finds plugins that failed to load, rows a failed update left behind, unmanaged entries - plus a disk scan for update leftovers.
★ 0
MIT
TypeScript
dsh plugin --profile web add @ragnoryok1/dsh-plugin-doctor