dsh-rule-engine 0.5.15 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -2
- package/lib/core/judge.js +29 -15
- package/lib/core/text-detect.js +36 -2
- package/package.json +1 -1
- package/scripts/release-plugin.mjs +22 -2
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# dsh-rule-engine
|
|
2
2
|
|
|
3
3
|

|
|
4
|
-

|
|
5
5
|
|
|
6
6
|
DSH 规则执行引擎 v3 的插件实现。它把 `~/.dsh/AGENTS.md` 当作唯一真相源,自动解析规则四要素与执行等级,再通过「工具守卫 + 文本检测 + 时序检查 + 审计台账」执行用户规则,而不是内置一套与用户无关的安全清单。
|
|
7
7
|
|
|
8
|
-
> 当前版本 **0.5.14
|
|
8
|
+
> 当前版本 **0.5.15**(npm 已发布;0.5.14 = 2026-09-01 已发布三通道:npm=0.5.14 / git 8105d3e / Release v0.5.14 带 tgz;上一版 0.5.13 = 2026-08-31,git 4c52982)。本插件面向"规则机器化执行":规则写在 AGENTS.md 里,引擎负责让它们真的被遵守;所有规则动态解析,规则增删改后无需重写插件。
|
|
9
9
|
|
|
10
10
|
## 项目背景
|
|
11
11
|
|
|
@@ -157,6 +157,20 @@ dsh plugin --profile web add dsh-rule-engine
|
|
|
157
157
|
- **自由区域(Free Zone)**:AGENTS.md 中 `<!-- free-zone:start -->` / `<!-- free-zone:end -->` 标记框住的区段**整区跳过**(不解析、不产生规则、不硬拦、不审计)——适合放“想生效但不想被机器强制”的软约束(如法律守则)。区内的 `### [规则 F<n>]` 条目由配套插件 dsh-rules-manager 在设置页/`/rules` 中可见可管理。新增自由规则请手动在标记内编写(`/rules add` 只会插入到 free-zone 之前),详见 dsh-rules-manager 的 README「📝 新增一条自由规则(零基础三步)」
|
|
158
158
|
- **禁用规则联动**:dsh-rules-manager 的「禁用规则」存储(`~/.dsh/disabled-rules.json`)会被引擎读取,被禁用的规则标记为 disabled,不参与硬拦/纠察;恢复启用后自动重新生效
|
|
159
159
|
|
|
160
|
+
## 权限、依赖、外部服务与失败边界(DSH STORE 披露)
|
|
161
|
+
|
|
162
|
+
> 面向插件商城(DSH STORE)自动审核与安装者;普通用户可跳过。
|
|
163
|
+
> 声明原则:只陈述实际能力,不因申请自动上架而省略或弱化;本插件因权限信号被商城保守标记,符合"高权限防护工具需 user-reviewed"的商城契约。
|
|
164
|
+
|
|
165
|
+
- **文件访问**:读写 `~/.dsh/rule-engine.log.jsonl`(审计台账)、`rule-engine-tools.json`(工具白名单)、`rule-engine-verify.json`(验证通过记录)、`rule-engine-turn-cards.json`(回合末判例卡片)、`disabled-rules.json`(读)、`rule-engine.json`(配置,读)、`AGENTS.md`(读 + watch 重载)。**写入仅限插件私有状态文件**;对用户业务文件的写动作只在用户规则触发的守卫流程内执行(如版本守卫的备份/回滚)。
|
|
166
|
+
- **网络**:仅 `lib/service.js` 两处**只读 GET**(`api.github.com` release 检查、`raw.githubusercontent.com` upgrade-impact),8 秒超时、无凭据、无请求体,URL 由 package.json 解析(不访问任意地址)。
|
|
167
|
+
- **命令执行**:无 `child_process`/子进程调用;"命令检测"= 对用户命令文本做正则分析(词表),不运行任何被检测的命令。
|
|
168
|
+
- **凭据**:读取环境变量 `DSH_LLM_PROVIDER` / `DSH_LLM_MODEL` / `DSH_WORKSPACE` 作为运行配置(非密钥);不读取 API Key、令牌;审计与注入消息不含凭据。
|
|
169
|
+
- **依赖**:无运行时 `dependencies`;peer 依赖 `@deepseek-ai/dsh-home-paths`、`@deepseek-ai/dsh-typert-protocol`(官方接口);`package.json` 无安装期生命周期脚本(无 preinstall/install/postinstall/prepare)。
|
|
170
|
+
- **外部服务**:同网络项;失败时静默降级(release 检查失败只影响升级提示展示,不阻断守卫)。
|
|
171
|
+
- **失败边界**:LLM 裁决/意图兜底不可用时 fail-closed(不投递、不误放);审计写入失败不阻断拦截(拦截先于落盘);词表/LLM 双通道判定,低置信不参与硬拦;守卫拒绝仅针对变更类动作,只读操作无条件放行。
|
|
172
|
+
- **权限等级**(保守自评):**高**(可写审计/白名单等持久状态、访问网络只读端点、读取环境变量配置)——建议安装前阅读上文「安全设计」并按需二次审查。
|
|
173
|
+
|
|
160
174
|
## 当前局限与后续优化路线
|
|
161
175
|
|
|
162
176
|
当前版本已经具备完整四层骨架,但距离“成熟”仍有距离。以下是一些**难度较高、尚未完全实现**的优化方向,欢迎社区共同推进:
|
package/lib/core/judge.js
CHANGED
|
@@ -43,21 +43,35 @@ export async function judgeViolation(ctx, state, sessionId, violation, text) {
|
|
|
43
43
|
const rawCfg = Array.isArray(state.configs)
|
|
44
44
|
? state.configs.find((c) => String(c.ruleId) === String(violation.ruleId))
|
|
45
45
|
: undefined;
|
|
46
|
-
const prompt =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
const prompt = violation.mode === "criticism"
|
|
47
|
+
? [
|
|
48
|
+
"你是 DSH 规则 22② 语义裁决器。判断下面这段【用户消息】是否构成对执行者的批评/指责/质问(规则 22②:用户批评不构成任何授权)。只输出 JSON,不要其他内容。",
|
|
49
|
+
'JSON 格式:{"violated": true|false, "note": "一句话理由"}',
|
|
50
|
+
"判定要点:",
|
|
51
|
+
"1. 批评/指责/质问 = 用户对执行者行为的否定、责备、反问指责(如“你又错了”“怎么还在做”“这不对吧”)——violated=true;",
|
|
52
|
+
"2. 普通疑问/信息咨询/技术提问(如“这个功能怎么用”“你确认了吗”)——violated=false;",
|
|
53
|
+
"3. 用户引述规则文本或转述他人的批评——violated=false;",
|
|
54
|
+
"4. 拿不准给 false(偏不打扰——这是语义提醒,不是硬拦)。",
|
|
55
|
+
"检测提示:" + String(violation.reason || ""),
|
|
56
|
+
"用户消息:",
|
|
57
|
+
text,
|
|
58
|
+
"只输出 JSON。"
|
|
59
|
+
].join("\n")
|
|
60
|
+
: [
|
|
61
|
+
"你是 DSH 规则裁决器。判断下面这段【执行者回复】是否违反【规则】。只输出 JSON,不要其他内容。",
|
|
62
|
+
'JSON 格式:{"violated": true|false, "note": "一句话理由"}',
|
|
63
|
+
"判定要点:",
|
|
64
|
+
"1. 回复若是在表达合规声明(如“我不再/将避免/规则 X 已自证/无重复推销”)或引述规则文本——violated=false;",
|
|
65
|
+
"2. 只有确实发生规则禁止的行为(如真的再次推销/真的发布未声明/真的遗漏事项)才 violated=true;",
|
|
66
|
+
"3. 拿不准时给出倾向性判断(false 偏不打扰)。",
|
|
67
|
+
"规则编号:" + violation.ruleId,
|
|
68
|
+
"规则标题:" + violation.title,
|
|
69
|
+
"检测提示:" + String(violation.reason || ""),
|
|
70
|
+
"规则正文:" + (rawCfg?.body || "(未提供)"),
|
|
71
|
+
"执行者回复:",
|
|
72
|
+
text,
|
|
73
|
+
"只输出 JSON。"
|
|
74
|
+
].join("\n");
|
|
61
75
|
const messages = [{ role: "user", content: [{ type: "text", text: prompt }] }];
|
|
62
76
|
let out = "";
|
|
63
77
|
for await (const chunk of ctx.llm.stream({
|
package/lib/core/text-detect.js
CHANGED
|
@@ -19,6 +19,19 @@ import { detectOverengineeringText } from "./overengineering.js";
|
|
|
19
19
|
// F2(2026-08-28 阶段三):交付声明强模式(规则 23④ verify-gap 词面)——
|
|
20
20
|
// 裸"完成"太宽("完成社区检索/尚未完成/正在完成"误触)→ 强完成声明 + 否定/进行态排除。
|
|
21
21
|
// 模块级导出(纯函数模块,可测试锁定);LLM 裁决层(deliverSuspects)兜底不变。
|
|
22
|
+
// 用户批评形态(规则 22② 机器提示,2026-09-02;2026-09-02 实弹修正分层):
|
|
23
|
+
// CRITICISM_RE(STRONG)= 辱骂词/连续问叹符 → 确定性批评,直接 B 级提醒(零成本零延迟);
|
|
24
|
+
// CRITICISM_WEAK_RE(WEAK) = 反问/责问形态 → 只产嫌疑(awaitingJudge),交 judge 裁决,
|
|
25
|
+
// 确认批评才提醒、拿不准不打扰——词表只产嫌疑、判定权归模型(兑现 v4.107/v4.113 拍板)。
|
|
26
|
+
// 检测对象=用户消息(session.lastUserText),命中=审计+注入"批评≠授权"提醒(B 级,不拦截)
|
|
27
|
+
export const CRITICISM_RE =
|
|
28
|
+
/(?:草泥马|你妈的|你妈|傻[逼B Xx]|煞笔|智障|神经病|你聋了|你瞎了|你疯了|你傻了|蠢货|废物|滚蛋|[??]{3,}|[!!]{4,})/;
|
|
29
|
+
|
|
30
|
+
// WEAK 嫌疑集:反问/责问/否定比较形态("怎么还在做/又错了/这不对吧/你是不是又…")。
|
|
31
|
+
// 允许偏宽(普通技术疑问如"怎么用"也会进入嫌疑)——判定权在 judge:模型确认才是提醒,拿不准=不打扰。
|
|
32
|
+
export const CRITICISM_WEAK_RE =
|
|
33
|
+
/(?:怎么(?:又|还|居然|仍然|老是|总)?[^,。!?]{0,14}(?:了|呢|啊|?|!|!)?|你(?:又|还|居然|咋|怎么)[^,。!?]{0,12}(?:了|呢|啊|吧)?|(?:又|还|总是|老是|仍然|居然)[^,。!?]{0,12}(?:了|呢|啊|吧)?|这(?:不|有点|根本|完全|哪)?(?:对|行|合理|应该|像话)(?:吧|么|吗|啊|呢)?|你(?:是不是|难道|难道说|该不会)[^,。!?]{0,10}(?:了|呢|吧)?|(?:搞什么|干什么|什么情况|怎么回事|搞砸|搞错了|犯什么错))/;
|
|
34
|
+
|
|
22
35
|
export const DELIVERY_RE = /(?:已完成|全部(?:[^\s,。;!?]{0,12})完成|已[^\s,。;!?]{0,8}完成|修复完成|落盘完成|验证[^\s,。;!?]{0,6}(?:通过|成功)|全部通过|全部[^\s,。;!?]{0,10}通过|已通过|已修复|搞定)(?:\s*了|!|!|,[^。]*)?(?![^。]*(?:尚未|没有|未|没|还没|未完|待做|待完成))/;
|
|
23
36
|
|
|
24
37
|
/** 从 assistant message 内容中提取纯文本 */
|
|
@@ -189,9 +202,30 @@ export function detectViolations({ configs, session, text, reasoningText = "", m
|
|
|
189
202
|
}
|
|
190
203
|
}
|
|
191
204
|
|
|
205
|
+
// 规则 22② 机器提示(2026-09-02;实弹修正:STRONG 直接提醒 / WEAK 只产嫌疑交 judge 裁决)
|
|
206
|
+
const cfg22 = byId.get("22");
|
|
207
|
+
if (cfg22 && session.lastUserText) {
|
|
208
|
+
const critText = session.lastUserText;
|
|
209
|
+
if (CRITICISM_RE.test(critText) && !isQuoteOrParaphraseContext(critText, CRITICISM_RE)) {
|
|
210
|
+
hits.push({
|
|
211
|
+
ruleId: "22",
|
|
212
|
+
title: cfg22.title,
|
|
213
|
+
kind: "correct",
|
|
214
|
+
reason: "用户消息含确定性批评形态(规则 22②/手册 5b-2):用户批评不构成任何授权——被批评后应停、按归因四问产出三件套、等指令;禁止因批评启动任何变更"
|
|
215
|
+
});
|
|
216
|
+
} else if (CRITICISM_WEAK_RE.test(critText) && !isQuoteOrParaphraseContext(critText, CRITICISM_WEAK_RE)) {
|
|
217
|
+
hits.push({
|
|
218
|
+
ruleId: "22",
|
|
219
|
+
title: cfg22.title,
|
|
220
|
+
kind: "self-certify",
|
|
221
|
+
mode: "criticism",
|
|
222
|
+
reason: "用户消息疑似批评/质问形态(规则 22②/手册 5b-2,待裁决):若确属批评——不构成任何授权,被批评后应停、按归因四问产出三件套、等指令;禁止因批评启动任何变更"
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
192
227
|
// 规则 31(2026-09-01 用户拍板):同回合同一只读工具 ≥3 次且思维链无验证目标语 → 提示(B 级:留痕+注入)
|
|
193
|
-
const rule31Cfg = byId.get("31");
|
|
194
|
-
if (rule31Cfg) {
|
|
228
|
+
const rule31Cfg = byId.get("31"); if (rule31Cfg) {
|
|
195
229
|
const counts = new Map();
|
|
196
230
|
const pending = session.turn?.pendingToolCalls;
|
|
197
231
|
if (pending && typeof pending.values === "function") {
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// 步骤:版本 bump -> 测试 -> npm pack -> npm publish -> git commit+push -> gh release(带 tgz asset)
|
|
12
12
|
// 安全:token 经环境变量注入,不在命令文本/日志中打印
|
|
13
13
|
import { execSync } from "node:child_process";
|
|
14
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "node:fs";
|
|
14
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, statSync } from "node:fs";
|
|
15
15
|
import { dirname, join, relative, resolve } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
|
|
@@ -219,7 +219,27 @@ const untrackedList = quiet(`cd /d "${repoRoot}" && git status --porcelain -- ${
|
|
|
219
219
|
if (untrackedList.length > 0) {
|
|
220
220
|
console.log(`(未跟踪文件 ${untrackedList.length} 个将一并提交:${untrackedList.slice(0, 6).join(", ")}${untrackedList.length > 6 ? " 等" : ""})`);
|
|
221
221
|
}
|
|
222
|
-
|
|
222
|
+
// 2026-09-02:提交物个人标识/本机路径扫描(git 提交侧门禁;与 publish-aptitude-check.mjs PERSONAL_RE 同源)
|
|
223
|
+
// 教训 8105d3e:logs/.analysis-tmp 手册草稿曾随 add -A 进入公开库——npm 侧有门禁,git 侧此前没有。
|
|
224
|
+
const PERSONAL_RE = /jilian|季涟|D:\\DeepSeek|D:\/DeepSeek|@qq\.com|@163\.com|@outlook\.com|私人注释|个人标识/;
|
|
225
|
+
function scanStagedPersonal(repoRoot, stageSpec) {
|
|
226
|
+
const files = quiet(`cd /d "${repoRoot}" && git diff --cached --name-only -- ${stageSpec || "."}`)
|
|
227
|
+
.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
228
|
+
const bad = [];
|
|
229
|
+
for (const f of files) {
|
|
230
|
+
const p = join(repoRoot, f);
|
|
231
|
+
if (!existsSync(p) || statSync(p).size > 2 * 1024 * 1024) continue;
|
|
232
|
+
const text = readFileSync(p, "utf8");
|
|
233
|
+
// 豁免:LICENSE/版权行=MIT 许可要求版权声明(作者署名是公开出版物必要内容,非泄露)
|
|
234
|
+
if (/^LICENSE/i.test(path.basename(f))) continue;
|
|
235
|
+
if (/Copyright\s*\(c\)/i.test(text)) continue;
|
|
236
|
+
if (PERSONAL_RE.test(text)) bad.push(f);
|
|
237
|
+
}
|
|
238
|
+
if (bad.length) fail(`提交物含个人标识/本机路径(PERSONAL_RE 命中 ${bad.length} 个文件):${bad.slice(0, 4).join(", ")};请脱敏后重试`);
|
|
239
|
+
}
|
|
240
|
+
run(`cd /d "${repoRoot}" && ${proxyPrefix()}git add -A -- ${stageSpec}`);
|
|
241
|
+
scanStagedPersonal(repoRoot, stageSpec);
|
|
242
|
+
run(`cd /d "${repoRoot}" && git -c core.autocrlf=false commit -m "release: ${name} v${nextVer}" || exit 0`);
|
|
223
243
|
run(`cd /d "${repoRoot}" && ${proxyPrefix()}git push "${pushUrl}" HEAD`);
|
|
224
244
|
|
|
225
245
|
// ── 6. GitHub Release(带正式 tgz asset,规则 26;清单标记 skipRelease 的包不建)─────
|