dsh-rule-engine 0.5.16 → 0.5.17

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 CHANGED
@@ -1,11 +1,11 @@
1
1
  # dsh-rule-engine
2
2
 
3
3
  ![npm](https://img.shields.io/npm/v/dsh-rule-engine)
4
- ![version](https://img.shields.io/badge/version-0.5.16-blue)
4
+ ![version](https://img.shields.io/badge/version-0.5.17-blue)
5
5
 
6
6
  DSH 规则执行引擎 v3 的插件实现。它把 `~/.dsh/AGENTS.md` 当作唯一真相源,自动解析规则四要素与执行等级,再通过「工具守卫 + 文本检测 + 时序检查 + 审计台账」执行用户规则,而不是内置一套与用户无关的安全清单。
7
7
 
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 里,引擎负责让它们真的被遵守;所有规则动态解析,规则增删改后无需重写插件。
8
+ > 当前版本 **0.5.16**(2026-09-02 已发布三通道;0.5.15 = 2026-09-02 判例登记批次(npm 0.5.15 / git tag / Release 带 tgz);0.5.14 = 2026-09-01 三通道,git 8105d3e)。本插件面向"规则机器化执行":规则写在 AGENTS.md 里,引擎负责让它们真的被遵守;所有规则动态解析,规则增删改后无需重写插件。
9
9
 
10
10
  ## 项目背景
11
11
 
@@ -67,6 +67,8 @@ DSH 规则执行引擎 v3 的插件实现。它把 `~/.dsh/AGENTS.md` 当作唯
67
67
  | 版本 | 日期 | 要点 |
68
68
  |---|---|---|
69
69
  | **0.5.14** | 2026-09-01 | 分点三柱(条件句零授权/显式命名对象锚定/clauseId 隔离)+ skill 词收紧 + 规则 5 引证检测扩展(内部引用无依据→审计注入)+ 规则 31 查证纪律(B+D)+ README 版本四性对齐 |
70
+ | **0.5.17** | 2026-09-03 | A1 规则 2 时间词拆组(当下词=Get-Date① / 历史日期=证据锚②,消除"引用历史日期必判未核对"误报)+ EVIDENCE_MARK_RE 增证据锚(commit hash/版本行/踩坑 N/版本记录) |
71
+ | **0.5.16** | 2026-09-02 | 批评≠授权检测双层重构(STRONG 直接提醒 / WEAK 嫌疑交 judge 裁决——实弹漏判"你怎么还在做!"修复;词表只产嫌疑+模型定论)+ 0.5.15 后批次(Remote 签名一致性回归/调试产物清理/PERSONAL_RE git 门禁/CRITICISM_RE 初版/LICENSE 豁免)+ DSH-STORE 权限披露 |
70
72
  | **0.5.15** | 2026-09-02 | 回合末裁决卡片(host 侧 turn/end 裁决摘要 + client 包 dsh-rule-engine-client:可交互 ✅/❌ 卡片;判例登记一次性(per-block);多次裁决一卡逐条分组;卡片/判例落盘 rule-engine-turn-cards.json 重启不丢) |
71
73
  | **0.5.13** | 2026-08-31 | 通用化(声明式绑定/禁用语义/会话寻址/验证通道/发布适用性门禁)+ 阶段二·三(委派豁免/中文顿号路径/ask 节流区分/D1-D3 注入/F1 规则 2/F2 verify-gap) |
72
74
  | **0.5.12** | 2026-08-30 | 意图优先级修正(动作词先于方案词)、LLM 意图兜底同步等待、@文件引用信号、只读判定三档(写特征/白名单/保守拒)、规则 22 粒度并入会话授权、ERR 码打标链路、F2 打标指纹、F5 契约类别白名单 |
@@ -77,6 +79,10 @@ DSH 规则执行引擎 v3 的插件实现。它把 `~/.dsh/AGENTS.md` 当作唯
77
79
  | **0.5.7** | 2026-08-26 | 注入噪音治理(词表只产嫌疑 + LLM 裁决 + fail-closed + 投递资格闸 + 审计完整性)、注入通道重入修复(宏任务投递)、语义层(awaitingJudge/judge-pass/false/unavailable) |
78
80
  | **0.5.6** | 2026-08-26 | 同回复聚合注入、已自证规则不重复触发、C2 规则统计(detected/suppressed/injected) |
79
81
 
82
+ ## 发行固定源
83
+
84
+ - **0.5.16(当前)** 固定于 main Commit `4d6673a5d8e4ed329e2b923d294a3743c643deb5`(40 位完整;`git checkout 4d6673a` 可复现 npm `dsh-rule-engine@0.5.16` 与 GitHub Release v0.5.16 同源代码——0.5.16 发布后 `96a3318`/`4d6673a` 为发布收尾提交:README 版本表与 B1/B2 修复,均属 0.5.16 源码)。
85
+
80
86
  ## 任务契约与反过度工程(可选)
81
87
 
82
88
  - 默认**关闭**;可在规则引擎设置页开启「任务边界与反过度工程」总开关。
package/lib/core/audit.js CHANGED
@@ -2,6 +2,9 @@
2
2
  // 每次拦截/纠察/自证都追加一行;超限自动裁剪保留尾部。
3
3
  // P2-9:裁剪改为惰性——每 APPEND_TRIM_INTERVAL 次追加才做一次大小检查,
4
4
  // 避免每次写入都 statSync(高频 deny/纠察时降低 IO)。
5
+ // B2(2026-09-03):判定来源可溯源字段约定(判例回灌前置)——
6
+ // verdictSource: "guard"(工具守卫 intercept,机器判定)| "lexicon"(词表直判/降级)
7
+ // | "judge"(LLM 裁决分流)| "llm-intent"(意图 LLM 兜底);缺失 = 历史记录(无此字段)。
5
8
  import { appendFileSync, readFileSync, statSync, writeFileSync } from "node:fs";
6
9
  import { randomUUID } from "node:crypto";
7
10
  import { auditFilePath } from "./paths.js";
@@ -143,6 +143,7 @@ function auditIntent(state, sid, text, verdict, ms, limitHit) {
143
143
  ? `词表低置信 → LLM:${JSON.stringify(verdict)}(延迟 ${ms}ms${limitHit ? " 限额" : ""})`
144
144
  : `词表低置信 → LLM 失败降级(延迟 ${ms}ms)`,
145
145
  session: sid,
146
+ verdictSource: verdict ? "llm-intent" : "lexicon",
146
147
  text: String(text).slice(0, 120)
147
148
  });
148
149
  } catch {
@@ -28,12 +28,18 @@ export const PROTECTED_FILENAME_RE =
28
28
  export const DATA_DIR_RE =
29
29
  /(?:^|[\\/])\.dsh[\\/](?:sessions|storages|\.backups)[\\/]/i;
30
30
 
31
+ /** 当下时间词(规则 2①:写当下相对时间前必须先 Get-Date 核对——A1 拆组,2026-09-03) */
31
32
  export const TIME_WORDS =
32
- /今天|昨天|前天|上周|本周|刚才|\d+\s*分钟前|\d{1,2}\s*月\s*\d{1,2}\s*日|\d{4}\s*年\s*\d{1,2}\s*月\s*\d{1,2}\s*日/;
33
+ /今天|昨天|前天|上周|本周|刚才|\d+\s*分钟前/;
33
34
 
34
- /** 事件时间证据标注(规则 2②:过去事件时间必须绑定事件自身证据;"之前/当时"等模糊词不触发检测、不采信) */
35
+ /** 历史日期/绝对日期(规则 2②:只要求事件证据锚,不要求 Get-Date——A1 拆组;含 ISO 格式,本机高频表述) */
36
+ export const HISTORIC_DATE_RE =
37
+ /\d{1,2}\s*月\s*\d{1,2}\s*日|\d{4}\s*年\s*\d{1,2}\s*月\s*\d{1,2}\s*日|20\d{2}[-/.]\d{1,2}[-/.]\d{1,2}/;
38
+
39
+ /** 事件时间证据标注(规则 2②:过去事件时间必须绑定事件自身证据;"之前/当时"等模糊词不触发检测、不采信)
40
+ * A1 增补证据锚(2026-09-03):commit hash / 版本号 / 踩坑 N / 版本记录 vX —— 历史日期带锚即视为已标注来源,免 Get-Date */
35
41
  export const EVIDENCE_MARK_RE =
36
- /日志\s*(?:ts|时间戳)|mtime|启动时间|进程\s*StartTime|文件\s*修改时间|来源[::]|ts\s*[:=]|Get-Date\s*输出|事件时间已核实|【时间未核实】/i;
42
+ /日志\s*(?:ts|时间戳)|mtime|启动时间|进程\s*StartTime|文件\s*修改时间|来源[::]|ts\s*[:=]|Get-Date\s*输出|事件时间已核实|【时间未核实】|\b[0-9a-f]{7,40}\b|\bv\d+(?:\.\d+){1,2}\b|踩坑\s*\d+|版本(?:记录)?\s*v\d+(?:\.\d+){1,2}/i;
37
43
 
38
44
  export const PROMISE_WORDS =
39
45
  /包在我身上|肯定能|绝对没问题|保证(?!不|无法)|一定可以|放心(?:,|,)?肯定|万无一失/;
@@ -5,6 +5,7 @@ import {
5
5
  PROMISE_WORDS,
6
6
  SOURCE_MARK,
7
7
  TIME_WORDS,
8
+ HISTORIC_DATE_RE,
8
9
  EVIDENCE_MARK_RE,
9
10
  URL_RE,
10
11
  TECH_TERM_RE,
@@ -107,8 +108,13 @@ export function isSelfCertified(text, ruleId) {
107
108
  * @returns {Array<{ruleId,title,kind,reason}>}
108
109
  */
109
110
  export function detectTimeRule(session, text, timeCfg) {
110
- if (!timeCfg || !TIME_WORDS.test(text) || isQuoteOrParaphraseContext(text, TIME_WORDS)) return [];
111
- if (!session?.turn?.getDateSeen) {
111
+ const hasNow = TIME_WORDS.test(text);
112
+ const hasHist = HISTORIC_DATE_RE.test(text);
113
+ if (!timeCfg || (!hasNow && !hasHist)) return [];
114
+ if (isQuoteOrParaphraseContext(text, TIME_WORDS) || isQuoteOrParaphraseContext(text, HISTORIC_DATE_RE)) return [];
115
+ // A1(2026-09-03 拆组):当下时间词才要求 Get-Date 核对(①);历史日期只走证据锚(②),
116
+ // 不再要求 Get-Date——消除"引用历史日期必判未核对"的误报(规则 2②:每时间点绑定自己证据)。
117
+ if (hasNow && !session?.turn?.getDateSeen) {
112
118
  return [{
113
119
  ruleId: "2",
114
120
  title: timeCfg.title,
@@ -116,12 +122,12 @@ export function detectTimeRule(session, text, timeCfg) {
116
122
  reason: "回答出现具体时间词/日期,但本回合未先调用 Get-Date 核对"
117
123
  }];
118
124
  }
119
- if (!EVIDENCE_MARK_RE.test(text)) {
125
+ if ((hasNow || hasHist) && !EVIDENCE_MARK_RE.test(text)) {
120
126
  return [{
121
127
  ruleId: "2",
122
128
  title: timeCfg.title,
123
129
  kind: "correct",
124
- reason: "回答含具体时间词但未附事件证据标注(日志 ts/文件 mtime/进程启动时间等)——Get-Date 当前时间不算过去事件证据(规则 2②)。正确动作:查该事件证据补(来源:…)或删时间词/标【时间未核实】,勿以当前时间替代"
130
+ reason: "回答含具体时间词/日期但未附事件证据标注(日志 ts/文件 mtime/进程启动时间/commit 锚/版本行/踩坑 N 等)——Get-Date 当前时间不算过去事件证据(规则 2②)。正确动作:查该事件证据补(来源:…)或删时间词/标【时间未核实】,勿以当前时间替代"
125
131
  }];
126
132
  }
127
133
  return [];
@@ -145,23 +151,24 @@ export function detectViolations({ configs, session, text, reasoningText = "", m
145
151
  const timeCfg = byId.get("2");
146
152
  // v0.5.11(用户定稿):① 只命中具体时间词(TIME_WORDS 为具体词表——"之前/当时"等模糊词不命中,不新增);
147
153
  // ② 具体时间词 + 无事件证据标注(日志 ts/文件 mtime/进程启动时间等)→ 违规,Get-Date 当前时间不算事件证据。
154
+ // A1(2026-09-03):时间词拆组——当下词(TIME_WORDS)才要求 Get-Date①;历史日期(HISTORIC_DATE_RE)只查证据锚②。
148
155
  // F1(2026-08-28 阶段三):本检测仅作"判定",投递时机由调用方在 turn/end 复核(见 detectTimeRule 注释)。
149
156
  // B3(2026-08-29):引述/转述语境的时间词不触发("你昨天说…"是转述,不是我的时间表述);
150
157
  // 第一人称"我说昨天…"仍触发(转述不了自己)。
151
- if (timeCfg && TIME_WORDS.test(text) && !isQuoteOrParaphraseContext(text, TIME_WORDS)) {
152
- if (!session.turn.getDateSeen) {
158
+ if (timeCfg && (TIME_WORDS.test(text) || HISTORIC_DATE_RE.test(text)) && !isQuoteOrParaphraseContext(text, TIME_WORDS) && !isQuoteOrParaphraseContext(text, HISTORIC_DATE_RE)) {
159
+ if (!session.turn.getDateSeen && TIME_WORDS.test(text)) {
153
160
  hits.push({
154
161
  ruleId: "2",
155
162
  title: timeCfg.title,
156
163
  kind: "correct",
157
164
  reason: "回答出现具体时间词/日期,但本回合未先调用 Get-Date 核对"
158
165
  });
159
- } else if (TIME_WORDS.test(text) && !EVIDENCE_MARK_RE.test(text)) {
166
+ } else if (!EVIDENCE_MARK_RE.test(text)) {
160
167
  hits.push({
161
168
  ruleId: "2",
162
169
  title: timeCfg.title,
163
170
  kind: "correct",
164
- reason: "回答含具体时间词但未附事件证据标注(日志 ts/文件 mtime/进程启动时间等)——Get-Date 当前时间不算过去事件证据(规则 2②)"
171
+ reason: "回答含具体时间词/日期但未附事件证据标注(日志 ts/文件 mtime/进程启动时间/commit 锚/版本行/踩坑 N 等)——Get-Date 当前时间不算过去事件证据(规则 2②)"
165
172
  });
166
173
  }
167
174
  }
package/lib/index.js CHANGED
@@ -327,7 +327,8 @@ async function deliverSuspects(ctx, sessionId, suspects, text) {
327
327
  name: "违规裁决",
328
328
  event: "assistant/message",
329
329
  reason: `${verdict.action}:${verdict.note || ""}${verdict.model ? `(model=${verdict.model})` : ""}`,
330
- session: sessionId
330
+ session: sessionId,
331
+ verdictSource: "judge"
331
332
  });
332
333
  return verdict.action === "deliver" ? v : null;
333
334
  } catch (error) {
@@ -1093,7 +1094,9 @@ export function handleSessionEvent(ctx, session, event) {
1093
1094
  name: v.title,
1094
1095
  event: "assistant/message",
1095
1096
  reason: v.reason,
1096
- session: sid
1097
+ session: sid,
1098
+ // B2(2026-09-03):判定来源可溯源(判例回灌前置:区分词表直判与 LLM 裁决分流)
1099
+ verdictSource: v.awaitingJudge ? "judge" : "lexicon"
1097
1100
  });
1098
1101
  }
1099
1102
  // v0.5.7 分流水线:B 级机器型(correct,词表=确定证据)立即投递;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-rule-engine",
3
- "version": "0.5.16",
3
+ "version": "0.5.17",
4
4
  "description": "DSH 规则执行引擎 v3:容器解析 AGENTS.md + 理解器 + 匹配机 + 执行框架",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -6,9 +6,10 @@
6
6
  // - --sync-profile:发布成功后自动更新 profiles/<profile>/pnpm-workspace.yaml 豁免名单
7
7
  // (备份原文件)+ 运行全量装配审计(写 .dsh 需以 danger-full-access 运行本脚本)
8
8
  // 前置:npm 已认证、gh 已认证、git 已配置。
9
- // 网络:默认直连(2026-08-27 实测 npm registry / github.com / api.github.com 直连可用,无需代理;
10
- // 代理挂掉时不再 ECONNREFUSED)。需要代理的环境设 DSH_RELEASE_PROXY=http://127.0.0.1:7890
11
- // 步骤:版本 bump -> 测试 -> npm pack -> npm publish -> git commit+push -> gh release(带 tgz asset
9
+ // 网络:2026-09-03 实测修正——直连 github.com/npm registry 已不可靠(443 超时/连接重置);
10
+ // 需代理环境请设 DSH_RELEASE_PROXY=http://127.0.0.1:7890(本机 Clash 7890)。未设时脚本仍尝试直连。
11
+ // 步骤:版本 bump -> 测试 -> npm pack -> npm publish -> [publish dist-tags 校验(B1,2026-09-03]
12
+ // -> git commit+push -> gh release(带 tgz asset)
12
13
  // 安全:token 经环境变量注入,不在命令文本/日志中打印
13
14
  import { execSync } from "node:child_process";
14
15
  import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, statSync } from "node:fs";
@@ -176,6 +177,26 @@ for (const readme of ["README.md", "README.en.md"]) {
176
177
  }
177
178
  console.log("版本已 bump(package.json + README 徽章)");
178
179
 
180
+ // B1(2026-09-03):README 版本表行自动插入(阶段 C 人脑核对已失效的自动化;幂等:已存在 nextVer 行则跳过)
181
+ // 位置:版本表(| 版本 | 日期 | 要点 |)表头后第一数据行之前(最新在上);内容用调用方 notes 参数或 git log 最近提交信息
182
+ const notes = process.argv.includes("--notes") ? process.argv[process.argv.indexOf("--notes") + 1] : "";
183
+ for (const readme of ["README.md"]) {
184
+ const rd = join(dir, readme);
185
+ if (!existsSync(rd)) continue;
186
+ let rtxt = readFileSync(rd, "utf8");
187
+ const tableHeader = "| 版本 | 日期 | 要点 |";
188
+ const hi = rtxt.indexOf(tableHeader);
189
+ if (hi < 0 || rtxt.includes(`| **${nextVer}** |`)) continue;
190
+ const today = new Date().toLocaleDateString("en-CA"); // 本地时区 YYYY-MM-DD
191
+ const summary = notes || (quiet(`cd /d "${dir}" && git log -1 --pretty=%s`).slice(0, 90) || "(待补变更摘要)");
192
+ const nl = rtxt.indexOf("\n", hi);
193
+ if (nl < 0) continue;
194
+ const row = `| **${nextVer}** | ${today} | ${summary.replace(/\|/g, "\\|")} |`;
195
+ rtxt = rtxt.slice(0, nl + 1) + row + "\n" + rtxt.slice(nl + 1);
196
+ writeFileSync(rd, rtxt, "utf8");
197
+ console.log(`[B1] ${readme} 版本表已插入 ${nextVer} 行(摘要来源:${notes ? "notes 参数" : "git log"});请核对内容`);
198
+ }
199
+
179
200
  // ── 3. 测试(规则 23:发布前运行时验证)─────────────────────────
180
201
  if (pkg.scripts && pkg.scripts.test) {
181
202
  console.log("\n=== 运行测试 ===");
@@ -193,6 +214,15 @@ if (!existsSync(join(dir, tgz))) fail(`打包产物缺失:${tgz}`);
193
214
  console.log("\n=== npm publish ===");
194
215
  run(`cd /d "${dir}" && ${proxyPrefix()}npm publish ${tgz}`);
195
216
 
217
+ // B1(2026-09-03):publish 后校验 registry dist-tags(0.5.16 事故:publish 自报成功但 latest 未切;
218
+ // 脚本此前只跑 publish 不校验发布态——三通道验证铁律:npm 通道以 dist-tags 为准,self-report 不算数)
219
+ console.log("\n=== npm 发布态校验(dist-tags)===");
220
+ const latestActual = quiet(`cd /d "${dir}" && ${proxyPrefix()}npm view ${name} dist-tags.latest`);
221
+ if (latestActual !== nextVer) {
222
+ fail(`npm 发布态异常:dist-tags.latest=${latestActual || "(为空)"},预期 ${nextVer}——请人工核查(可能 staged/缓存,见踩坑 117);勿继续 git/Release 通道`);
223
+ }
224
+ console.log(`dist-tags.latest=${latestActual} ✓`);
225
+
196
226
  // ── 5. git commit + push(token 经环境变量注入 URL,命令文本不含密钥明文)─────
197
227
  console.log("\n=== git commit + push ===");
198
228
  // 2026-08-25 修复 pushUrl 双重拼接:origin 可能是 ssh(git@github.com:)、https(https://github.com/)