🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

批量工单预筛 Phocinae/dsh-batch-triage

DeepSeek 客户端
Phocinae11 天前有提交已提供中文文档扫描:未发现敏感能力兼容 / 相关生态

用规则先给工单分类,拿不准的转人工复核

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/21 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/Phocinae/dsh-batch-triage.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 11 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🟢 扫描:未发现敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。

扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DeepSeek Harness 的分诊以确定性优先:规则预过滤器给每张工单分类,规则无法归类的都标为 needs_review,绝不猜测。

README

由 DeepSeek 最新模型翻译生成
dsh-batch-triage 规则决定简单的工单。规则放不下的,交给人工——绝不悄悄归为常规。 一个 DeepSeek Harness 插件。不用模型,不用服务,不联网,不需要 API key。 $ node -e " import('dsh-batch-triage').then(m => { const r = m.triageBatch([ { id: 'a', text: 'The service is down for all users' }, { id: 'b', text: 'How do I install this on Windows?' }, { id: 'c', text: 'The frobnitz is blorp.' }, ], m.resolveConfig({})); console.log(r.items.map(i => i.id + ' ' + i.class + ' ' + i.reasonCodes[0]).join('\n')); console.log('exit', r.exitCode, r.exitBasis); })" a urgent OUTAGE_REPORTED b routine INSTALL_QUESTION c needs_review NO_RULE_MATCHED
展开完整文档(README 剩余 1 节)
dsh-batch-triage

规则决定简单的工单。规则放不下的,交给人工——绝不悄悄归为常规。

一个 DeepSeek Harness 插件。不用模型,不用服务,不联网,不需要 API key。

$ node -e "
import('dsh-batch-triage').then(m => {
const r = m.triageBatch([
{ id: 'a', text: 'The service is down for all users' },
{ id: 'b', text: 'How do I install this on Windows?' },
{ id: 'c', text: 'The frobnitz is blorp.' },
], m.resolveConfig({}));
console.log(r.items.map(i => i.id + ' ' + i.class + ' ' + i.reasonCodes[0]).join('\n'));
console.log('exit', r.exitCode, r.exitBasis);
})"

a urgent OUTAGE_REPORTED
b routine INSTALL_QUESTION
c needs_review NO_RULE_MATCHED
exit 6 modelUnavailable

快速开始

dsh plugin --profile web add dsh-batch-triage@0.1.4

batch_triage 现在可用了;规则能放下的工单就放下;放不下的返回 needs_review——绝不悄悄归为常规。

为什么规则优先

一批工单不适合每条都调一次模型:量大,值得看的少,而且每条都要一次往返,不管它需不需要。所以顺序和直觉相反。

预筛先跑每一条。 规则放下明确的案例——带堆栈的崩溃是紧急,「怎么安装」是常规——而且不追问任何东西就放下。只有剩下的残渣才可能送到模型,而这个版本根本不会送到模型:它会直接说明。

这个顺序不是优化。它是设计的主路径,也改变了这个工具的定位:预筛才是产品,模型只是兜底。

最重要的一条规则

除非有规则说了,否则一条工单永远不会是 routine。

所有无法分类的路径——正文为空、没有匹配规则、没配模型、预算耗尽——都产出 needs_review。把「我看不出来」变成「没什么可看的」,正是这个工具要防的唯一失败,测试套件从两个方向都钉住了它。

0.1.2 收紧了「有规则说了」允许的含义。规则依据的是短语,而一张工单不止一句话:当匹配到的短语是个问题,而另一句报告了某处坏了,问题规则就不能决定这张工单。它变成 needs_review,带 MALFUNCTION_REPORTED,而那条本来会吞掉它的规则留在 reasonCodes 里,让读者看到两半。

decidedBy 说明实际是哪一层决定的:

| 值 | 含义 |
| prefilter | 规则放下了它;reasonCodes 指明是哪条 |
| unavailable | 什么都放不下,也没配模型层 |
| budget | 触到上限,所以没评估 |

后两个都带 escalate: true。两者都不是对工单的判定。

规则看什么

结构化字段优先于正文:severity: critical、security: true、data_loss: true、reporter_tier: enterprise。报告人填过表单,就已经告诉你了,正文里任何措辞都压不低字段给出的结论。措辞报告的情况更糟,照样往上抬 —— 字段永远埋不掉一次故障,两者冲突时,条目同时带上两个原因码。

否则以任何规则声称的最高类别为准,表格按强度从高到低排列,同一类别内并列时取先列出的规则:

| 类别 | 规则 |
| urgent | outage、data_loss、security、crash_with_trace、blocked_workflow |
| needs_review | regression、wrong_result、performance、flaky |
| routine | install_question、usage_question、documentation、feature_request、thanks |

一张工单既报告故障、又把自己写成提问,绝不能被提问规则吞掉。这是一条测试 —— 而在 0.1.1 里,它是一条不可能失败的测试,因为它的测试夹具用了 outage 规则已经识别的措辞,夹具和规则共享同一个假设,优先级从未真正被检验。现在优先级按类别判定,不再按位置,这也意味着调用方传入的 rules 数组无法重排表格、把判定变成 fail-open。

只靠类别优先级还不够,下面六张实测工单就是原因。outage 不认识「returns 500 for every request」,于是没有任何严重度规则命中,提问规则仅凭其中一句话就定了整张工单。所以 routine 判定要多过一道检查:如果正文报告了故障 —— 报错、崩溃、「unusable」、5xx、记录被删、凭据泄露 —— 而措辞没有任何严重度规则能识别,条目就变成 needs_review,原因码 MALFUNCTION_REPORTED,不再是 routine。这道守卫只会拒绝 routine;它抬不到 urgent,也碰不了严重度规则已经做出的判定。

按类别定优先级,是刻意放弃「最具体者胜」。打分系统需要没人能辩护的权重;类别阶梯一个都不需要,表格还保持可检查 —— 一条从不触发的规则,是你一眼能看见的 bug。

修复所对照的措辞,逐字摘自审计。六张工单在 0.1.1 上全是 routine,退出码 0:

| 工单 | 0.1.1 | 0.1.2 |
| How do I install this? Also our production API returns 500 for every request. | routine INSTALL_QUESTION,退出码 0 | urgent OUTAGE_REPORTED,退出码 1 |
| Is there documentation for the auth flow? We are seeing records deleted from our database. | routine DOCUMENTATION_REQUEST,退出码 0 | urgent DATA_LOSS_REPORTED,退出码 1 |
| Feature request: please add CSV export. Meanwhile the app is unusable for our whole team. | routine FEATURE_REQUEST,退出码 0 | urgent OUTAGE_REPORTED,退出码 1 |
| How do I configure SSRF protection? Our pentest found we can read other users files. | routine INSTALL_QUESTION,退出码 0 | urgent SECURITY_REPORTED,退出码 1 |
| where can i find the logs? the service has been returning 503 all morning | routine INSTALL_QUESTION,退出码 0 | urgent OUTAGE_REPORTED,退出码 1 |
| What is the right way to rotate keys? Our API token leaked in a public repo. | routine USAGE_QUESTION,退出码 0 | urgent SECURITY_REPORTED,退出码 1 |

只是提问的工单不受影响:How do I install this on Windows? 仍然是 routine,一批全是 routine 条目也仍然退出 0。这道守卫是下限,不是偏向紧急。

守卫的代价,先说清楚,别等以后才发现。 它有意保守,所以只要提问提到失败类词,也会被上调:How do I handle errors returned by the parser? 输出 needs_review,而不是 routine。分类变了,退出码没变,因为预过滤器判定的 needs_review 在本次发布里不算发现项。这是真实存在的遗留缺口——守卫自己产出的条目仍然退出 0,所以上面六个例子必须凭自身理由落到 urgent,而不能只停在 needs_review。这一点写进了 CHANGELOG,而不是留给读者自己发现。

退出码

| 退出码 | 含义 |
| 0 | 已运行,且无需人工处理 |
| 1 | 已运行并发现问题——不是失败 |
| 2 | 调用无效(空批次、超出上限、条目格式错误) |
| 3 | 输入或策略无法执行(id 重复) |
| 4 | 空扫描 |
| 5 | 记录的预期未满足 |
| 6 | 所有需要模型的条目都被跳过 |
| 7 | 任何条目都还没到达模型,上限就已经是零 |

它们之间的优先级是 lib/exit.mjs 里一条有文档的阶梯,因为一次运行可能同时满足多个条件,顺序决定你被告知哪一个:

2 usage → 3 config → 4 empty scan → 7 zero budget →
6 model unavailable → 5 expectation → 1 findings → 0 clean

1–64 个条目的批次只能到达 0、1、2、3 和 6。另外三个是阶梯上的层级,这个批次运行器没有对应的产生者:

* 4 空扫描被 2 抢先:空批次在任何条目被扫描之前就作为用法错误拒绝,所以这里 emptyScan 永远不会为真。第 4 级是为直接调用 exitCode() 的调用方准备的。
* 5 预期完全没有产生者——本次发布没有 expect API。这一级是预留并写入文档的,不是凭空发明的。
* 7 零预算在加载时就被拒绝,因为 modelStateCap: 0 是配置错误(见下文)。7 只能通过 exitCode() 本身到达,或者通过一个从未经过 resolveConfig 的配置对象到达。

把阶梯读成「这些就是你将看到的全部结果」在另一个方向上也是错的;它是完整的优先级顺序,不是可观察退出码的完整清单。
其中两种排序值得说明:

* 用法错误优先于一切。 一次从未有效的运行不可能发现任何东西,所以报告发现就是在歪曲事实。
* 覆盖缺口优先于发现。 6 表示「这次运行没能覆盖它需要覆盖的全部内容」,这比 1 的「其中一部分需要人工处理」更强——而且它是超集,因为对 6 做出反应的调用方会去查看,并会在摘要里找到列出的紧急项。

第二种排序的隐患在于:调用方只读退出码,就断定这批没问题。所以摘要始终列出 urgentIds 和 escalatedIds,并有测试断言紧急项在覆盖缺口旁边依然保留。

对 CI 而言: {0, 1, 6} 都是成功运行,导出为 CI_ACCEPTED。把 1 当作失败的任务,会在每一批值得运行的批次上变红。

上限是决策,不是静默丢弃

预算负担不起的项是 needs_review,带 BUDGET_EXHAUSTED,摘要会统计 budgetRefusals。被截断的运行不能读起来像完整运行。

modelStateCap: 0 在加载时就被拒绝,而不是接受。它在同一次调用里禁止了调用方所要求的东西,把这种矛盾变成分诊结果,等于让工单为配置错误背锅。拒绝以 TRIAGE_BAD_CONFIG 报告,退出码 2,不是 3:上限为零是自相矛盾的请求,阶梯把它归类为无效调用,而不是无法读取的输入。没有任何东西到达模型,所以第 7 级永远不会到达——见退出码表下方的说明。

限制与校验

每次调用 1–64 项,其中 64 是默认的 maxItems 上限,不是它唯一能取的值:运维调高 maxItems,批次上限也随之调高,而预过滤器对批次大小是线性的。超过上限会被拒绝,而不是截断,因为截断的批次报告的是恰好先到达的那些项的摘要——读起来和整批的摘要一模一样。重复 id 会被拒绝,而不是合并:id 是答案的键,合并会丢掉一张工单,且无从分辨丢的是哪张。这种拒绝是退出码 3,不是 2:批次本身格式正确,无法满足的是身份映射。

failOn 做什么

failOn 决定哪些类别算作发现(退出码 1):

| 值 | 发现是…… |
| any(默认) | 紧急项、任何升级,或任何 needs_review |
| urgent | 仅紧急项 |
| none | 什么都不算——调用方要的是报告,不是闸门 |

needs_review 是「应该有人看看」的决策,无论它来自预过滤规则(regression、wrong_result、performance、flaky,字段 severity: high|medium)还是来自残留项(no rule matched、模型不可用、预算耗尽)。0.1.3 之前只有残留项计入,所以一批包含单张回归工单退出码是 0 clean,可规则早就说过它需要人工处理。这个缺口已经补上:在 any 下,规则判定的 needs_review 也算一条 finding。

退出码仍然优先于 finding。残留项没有模型客户端时,先退出 6(modelUnavailable),排在 finding 判定之前;用法或配置错误又优先于这两者。所以已发布的插件有两个可见情形:

| 批次 | failOn 的效果 |
| 含残留项(没有规则匹配) | 无论 failOn 是什么,都退出 6 |
| 每一项都已判定,至少一项为 needs_review | any → 1;urgent / none → 0 |

modelClient 是库级选项,故意不放进下面的 YAML 配置块:它是给测试用的接缝,也供宿主以编程方式提供客户端。本版本仍然从不调用它——modelClass 保持 unavailable。

从 0.1.2 迁移

如果某个 CI 任务依赖 failOn: any 对某批次返回 0,而该批次唯一的问题是规则判定的 needs_review(regression、wrong_result、performance、flaky,或 severity: high|medium),那么这个批次现在会退出 1。

挑你真正想要的政策:

- failOn: urgent —— 只有 urgent 项会让运行失败。这并非旧行为的精确还原:它还会忽略残留升级项,而旧的 any 是计入这些项的。
- failOn: none —— 只报告,从不拦截。

这个改动是有意为之:规则说了「该有人看一眼」,就不该产出绿色运行。failOn: any 仍是默认值。

tau 在这里能做什么、不能做什么

tau 是置信度上的升级阈值,而本版本没有置信度可供比较。所以它只做设计允许的那一件事:能让 needs_review 项升级,并且从不降级任何东西。一个被接受却毫无效果的选项,比一个被拒绝的选项更糟,因为调用方会以为某项检查已经做过。

配置

- insert:
- id: batch_triage
name: dsh-batch-triage
config:
maxItems: 64
tau: null
modelStateCap: null
tokenCap: null
failOn: any # any | urgent | none
timeoutMs: 20000
batchSize: 16

本版本中不起作用的选项

有四个被接受的键在没有模型层时不起作用。把它们列在一起,是因为另一种做法——默默接受——正是本项目在自身文档别处批评的行为。

| 键 | 状态 |
| policyId | 接受,但不加载任何策略文件 |
| tokenCap | 接受,但没有任何东西上报 input_tokens 来对照执行 |
| timeoutMs | 接受,但没有任何东西启动子进程去超时 |
| batchSize | 接受,但没有任何东西被发送到某处去分批 |

它们仍会做范围校验,所以 timeoutMs: 0 在加载时会被拒绝。这是有意为之,不是为了整洁:校验这部分以后仍然是对的当分级落地时,现在移除它意味着以后要在压力下重新加回来。

failOn 是个特例,值得单独说一句:它在已发布的路径上生效。一批条目全部判定完毕,其中至少有一个 needs_review,在 any 下退出码为 1,在 urgent / none 下为 0。只有残留条目(没有规则能归类、且没有模型客户端的条目)在 6 处压过发现层级。

规则遇到不认识的 severity 会怎样

不认识的 severity——P4、banana、kritisch——不会被当作缺失处理。丢掉它,真实的优先级就会悄悄变成 routine;提升为 urgent 又会凭空造出信号。所以它变成 needs_review,附带 FIELD_SEVERITY_UNRECOGNISED:字段对优先级说了些什么,而本版本不知道是什么,这属于提问,而不是往任一方向猜。

识别的词表故意做得很宽(最高层级接受 critical、blocker、sev1、s1、P0、urgent、emergency、showstopper、severity 1、1、highest),因为 0.1.0 只接受四个标签,severity: 'P0' 当时的行为跟完全没有 severity 一模一样。

本版本不包含的内容

模型层级。 设计规定用本地 150M Phocinae 模型作为残留条目的兜底,服务不可达时采用 fail-closed 规则。它不在本版本里。输出已经带上它将来要填的字段(modelClass、confidence、top2Margin、score),modelClass 报告为 unavailable,因为这是事实。

省略的理由和这个家族到处适用的一样:可测量的价值在确定性层级,而增加模型调用的层级需要和其他一切相同的证据标准——测量,而不是假设。如实把字段发布为 unavailable,好过发布一个未经测量的层级。

同样不在本版本里:policyId(从文件加载的具名预过滤策略)和 tokenCap 在模型会报告 input_tokens 之后的强制执行。两者离开模型层级都是空转。

环境要求

Node ^22.19.0 || >=24.0.0。无运行时依赖。@deepseek-ai/cordis 是可选 peer。

测试

npm test # 45 test cases, no host required
npm run verify:artifacts # what npm publish would actually ship

两者都在发布路径上运行:prepack 是 npm test && npm run verify:artifacts,所以停止触发的规则无法发布。0.1.2 之前可以——verify:artifacts 为每条规则都带了正样本,本来会变红,但发布途中没有任何东西运行它。

node --test 报告的是测试用例数,不是断言数:这套测试是 45 个用例,包含大约 170 个 assert 调用,很多在循环里。用例循环时这个区别很重要——一个用例在五次迭代的第三次失败,算一次失败,单看用例数不会告诉你失败在哪。

验证器检查行为,不只是结构:它运行真实的预过滤器,并且断言表里每条规则都仍能命中。匹配不上的规则,和一条什么都没找到的规则,两者无法区分;在这个检查出现之前,这个项目的家族里已经两次出现这种确切的故障。

随包发布两个技能产物,它们不是同一份文本:

* lib/skill.mjs 从实时规则表生成正文,createPlugin 向宿主注册的就是它。
* skills/batch-triage/SKILL.md 是手写文件,随包一起走。没有任何东西比对这两者,校验器只断言文件存在、且被 files[] 覆盖。改了规则表,这一份就得你自己更新。

License

Apache-2.0。
预览分享卡片长什么样 ↓Phocinae/dsh-batch-triage
分享 · DeepSeek Harness Hub
Phocinae/dsh-batch-triage

用规则先给工单分类,拿不准的转人工复核

回到顶部