dsh-rule-engine 0.5.15 → 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 +22 -2
- package/lib/core/audit.js +3 -0
- package/lib/core/judge.js +29 -15
- package/lib/core/llm-intent.js +1 -0
- package/lib/core/patterns.js +9 -3
- package/lib/core/text-detect.js +51 -10
- package/lib/index.js +5 -2
- package/package.json +1 -1
- package/scripts/release-plugin.mjs +55 -5
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.
|
|
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
|
- 默认**关闭**;可在规则引擎设置页开启「任务边界与反过度工程」总开关。
|
|
@@ -157,6 +163,20 @@ dsh plugin --profile web add dsh-rule-engine
|
|
|
157
163
|
- **自由区域(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
164
|
- **禁用规则联动**:dsh-rules-manager 的「禁用规则」存储(`~/.dsh/disabled-rules.json`)会被引擎读取,被禁用的规则标记为 disabled,不参与硬拦/纠察;恢复启用后自动重新生效
|
|
159
165
|
|
|
166
|
+
## 权限、依赖、外部服务与失败边界(DSH STORE 披露)
|
|
167
|
+
|
|
168
|
+
> 面向插件商城(DSH STORE)自动审核与安装者;普通用户可跳过。
|
|
169
|
+
> 声明原则:只陈述实际能力,不因申请自动上架而省略或弱化;本插件因权限信号被商城保守标记,符合"高权限防护工具需 user-reviewed"的商城契约。
|
|
170
|
+
|
|
171
|
+
- **文件访问**:读写 `~/.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 重载)。**写入仅限插件私有状态文件**;对用户业务文件的写动作只在用户规则触发的守卫流程内执行(如版本守卫的备份/回滚)。
|
|
172
|
+
- **网络**:仅 `lib/service.js` 两处**只读 GET**(`api.github.com` release 检查、`raw.githubusercontent.com` upgrade-impact),8 秒超时、无凭据、无请求体,URL 由 package.json 解析(不访问任意地址)。
|
|
173
|
+
- **命令执行**:无 `child_process`/子进程调用;"命令检测"= 对用户命令文本做正则分析(词表),不运行任何被检测的命令。
|
|
174
|
+
- **凭据**:读取环境变量 `DSH_LLM_PROVIDER` / `DSH_LLM_MODEL` / `DSH_WORKSPACE` 作为运行配置(非密钥);不读取 API Key、令牌;审计与注入消息不含凭据。
|
|
175
|
+
- **依赖**:无运行时 `dependencies`;peer 依赖 `@deepseek-ai/dsh-home-paths`、`@deepseek-ai/dsh-typert-protocol`(官方接口);`package.json` 无安装期生命周期脚本(无 preinstall/install/postinstall/prepare)。
|
|
176
|
+
- **外部服务**:同网络项;失败时静默降级(release 检查失败只影响升级提示展示,不阻断守卫)。
|
|
177
|
+
- **失败边界**:LLM 裁决/意图兜底不可用时 fail-closed(不投递、不误放);审计写入失败不阻断拦截(拦截先于落盘);词表/LLM 双通道判定,低置信不参与硬拦;守卫拒绝仅针对变更类动作,只读操作无条件放行。
|
|
178
|
+
- **权限等级**(保守自评):**高**(可写审计/白名单等持久状态、访问网络只读端点、读取环境变量配置)——建议安装前阅读上文「安全设计」并按需二次审查。
|
|
179
|
+
|
|
160
180
|
## 当前局限与后续优化路线
|
|
161
181
|
|
|
162
182
|
当前版本已经具备完整四层骨架,但距离“成熟”仍有距离。以下是一些**难度较高、尚未完全实现**的优化方向,欢迎社区共同推进:
|
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";
|
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/llm-intent.js
CHANGED
|
@@ -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 {
|
package/lib/core/patterns.js
CHANGED
|
@@ -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
|
|
33
|
+
/今天|昨天|前天|上周|本周|刚才|\d+\s*分钟前/;
|
|
33
34
|
|
|
34
|
-
/**
|
|
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
|
|
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
|
/包在我身上|肯定能|绝对没问题|保证(?!不|无法)|一定可以|放心(?:,|,)?肯定|万无一失/;
|
package/lib/core/text-detect.js
CHANGED
|
@@ -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,
|
|
@@ -19,6 +20,19 @@ import { detectOverengineeringText } from "./overengineering.js";
|
|
|
19
20
|
// F2(2026-08-28 阶段三):交付声明强模式(规则 23④ verify-gap 词面)——
|
|
20
21
|
// 裸"完成"太宽("完成社区检索/尚未完成/正在完成"误触)→ 强完成声明 + 否定/进行态排除。
|
|
21
22
|
// 模块级导出(纯函数模块,可测试锁定);LLM 裁决层(deliverSuspects)兜底不变。
|
|
23
|
+
// 用户批评形态(规则 22② 机器提示,2026-09-02;2026-09-02 实弹修正分层):
|
|
24
|
+
// CRITICISM_RE(STRONG)= 辱骂词/连续问叹符 → 确定性批评,直接 B 级提醒(零成本零延迟);
|
|
25
|
+
// CRITICISM_WEAK_RE(WEAK) = 反问/责问形态 → 只产嫌疑(awaitingJudge),交 judge 裁决,
|
|
26
|
+
// 确认批评才提醒、拿不准不打扰——词表只产嫌疑、判定权归模型(兑现 v4.107/v4.113 拍板)。
|
|
27
|
+
// 检测对象=用户消息(session.lastUserText),命中=审计+注入"批评≠授权"提醒(B 级,不拦截)
|
|
28
|
+
export const CRITICISM_RE =
|
|
29
|
+
/(?:草泥马|你妈的|你妈|傻[逼B Xx]|煞笔|智障|神经病|你聋了|你瞎了|你疯了|你傻了|蠢货|废物|滚蛋|[??]{3,}|[!!]{4,})/;
|
|
30
|
+
|
|
31
|
+
// WEAK 嫌疑集:反问/责问/否定比较形态("怎么还在做/又错了/这不对吧/你是不是又…")。
|
|
32
|
+
// 允许偏宽(普通技术疑问如"怎么用"也会进入嫌疑)——判定权在 judge:模型确认才是提醒,拿不准=不打扰。
|
|
33
|
+
export const CRITICISM_WEAK_RE =
|
|
34
|
+
/(?:怎么(?:又|还|居然|仍然|老是|总)?[^,。!?]{0,14}(?:了|呢|啊|?|!|!)?|你(?:又|还|居然|咋|怎么)[^,。!?]{0,12}(?:了|呢|啊|吧)?|(?:又|还|总是|老是|仍然|居然)[^,。!?]{0,12}(?:了|呢|啊|吧)?|这(?:不|有点|根本|完全|哪)?(?:对|行|合理|应该|像话)(?:吧|么|吗|啊|呢)?|你(?:是不是|难道|难道说|该不会)[^,。!?]{0,10}(?:了|呢|吧)?|(?:搞什么|干什么|什么情况|怎么回事|搞砸|搞错了|犯什么错))/;
|
|
35
|
+
|
|
22
36
|
export const DELIVERY_RE = /(?:已完成|全部(?:[^\s,。;!?]{0,12})完成|已[^\s,。;!?]{0,8}完成|修复完成|落盘完成|验证[^\s,。;!?]{0,6}(?:通过|成功)|全部通过|全部[^\s,。;!?]{0,10}通过|已通过|已修复|搞定)(?:\s*了|!|!|,[^。]*)?(?![^。]*(?:尚未|没有|未|没|还没|未完|待做|待完成))/;
|
|
23
37
|
|
|
24
38
|
/** 从 assistant message 内容中提取纯文本 */
|
|
@@ -94,8 +108,13 @@ export function isSelfCertified(text, ruleId) {
|
|
|
94
108
|
* @returns {Array<{ruleId,title,kind,reason}>}
|
|
95
109
|
*/
|
|
96
110
|
export function detectTimeRule(session, text, timeCfg) {
|
|
97
|
-
|
|
98
|
-
|
|
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) {
|
|
99
118
|
return [{
|
|
100
119
|
ruleId: "2",
|
|
101
120
|
title: timeCfg.title,
|
|
@@ -103,12 +122,12 @@ export function detectTimeRule(session, text, timeCfg) {
|
|
|
103
122
|
reason: "回答出现具体时间词/日期,但本回合未先调用 Get-Date 核对"
|
|
104
123
|
}];
|
|
105
124
|
}
|
|
106
|
-
if (!EVIDENCE_MARK_RE.test(text)) {
|
|
125
|
+
if ((hasNow || hasHist) && !EVIDENCE_MARK_RE.test(text)) {
|
|
107
126
|
return [{
|
|
108
127
|
ruleId: "2",
|
|
109
128
|
title: timeCfg.title,
|
|
110
129
|
kind: "correct",
|
|
111
|
-
reason: "
|
|
130
|
+
reason: "回答含具体时间词/日期但未附事件证据标注(日志 ts/文件 mtime/进程启动时间/commit 锚/版本行/踩坑 N 等)——Get-Date 当前时间不算过去事件证据(规则 2②)。正确动作:查该事件证据补(来源:…)或删时间词/标【时间未核实】,勿以当前时间替代"
|
|
112
131
|
}];
|
|
113
132
|
}
|
|
114
133
|
return [];
|
|
@@ -132,23 +151,24 @@ export function detectViolations({ configs, session, text, reasoningText = "", m
|
|
|
132
151
|
const timeCfg = byId.get("2");
|
|
133
152
|
// v0.5.11(用户定稿):① 只命中具体时间词(TIME_WORDS 为具体词表——"之前/当时"等模糊词不命中,不新增);
|
|
134
153
|
// ② 具体时间词 + 无事件证据标注(日志 ts/文件 mtime/进程启动时间等)→ 违规,Get-Date 当前时间不算事件证据。
|
|
154
|
+
// A1(2026-09-03):时间词拆组——当下词(TIME_WORDS)才要求 Get-Date①;历史日期(HISTORIC_DATE_RE)只查证据锚②。
|
|
135
155
|
// F1(2026-08-28 阶段三):本检测仅作"判定",投递时机由调用方在 turn/end 复核(见 detectTimeRule 注释)。
|
|
136
156
|
// B3(2026-08-29):引述/转述语境的时间词不触发("你昨天说…"是转述,不是我的时间表述);
|
|
137
157
|
// 第一人称"我说昨天…"仍触发(转述不了自己)。
|
|
138
|
-
if (timeCfg && TIME_WORDS.test(text) && !isQuoteOrParaphraseContext(text, TIME_WORDS)) {
|
|
139
|
-
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)) {
|
|
140
160
|
hits.push({
|
|
141
161
|
ruleId: "2",
|
|
142
162
|
title: timeCfg.title,
|
|
143
163
|
kind: "correct",
|
|
144
164
|
reason: "回答出现具体时间词/日期,但本回合未先调用 Get-Date 核对"
|
|
145
165
|
});
|
|
146
|
-
} else if (
|
|
166
|
+
} else if (!EVIDENCE_MARK_RE.test(text)) {
|
|
147
167
|
hits.push({
|
|
148
168
|
ruleId: "2",
|
|
149
169
|
title: timeCfg.title,
|
|
150
170
|
kind: "correct",
|
|
151
|
-
reason: "
|
|
171
|
+
reason: "回答含具体时间词/日期但未附事件证据标注(日志 ts/文件 mtime/进程启动时间/commit 锚/版本行/踩坑 N 等)——Get-Date 当前时间不算过去事件证据(规则 2②)"
|
|
152
172
|
});
|
|
153
173
|
}
|
|
154
174
|
}
|
|
@@ -189,9 +209,30 @@ export function detectViolations({ configs, session, text, reasoningText = "", m
|
|
|
189
209
|
}
|
|
190
210
|
}
|
|
191
211
|
|
|
212
|
+
// 规则 22② 机器提示(2026-09-02;实弹修正:STRONG 直接提醒 / WEAK 只产嫌疑交 judge 裁决)
|
|
213
|
+
const cfg22 = byId.get("22");
|
|
214
|
+
if (cfg22 && session.lastUserText) {
|
|
215
|
+
const critText = session.lastUserText;
|
|
216
|
+
if (CRITICISM_RE.test(critText) && !isQuoteOrParaphraseContext(critText, CRITICISM_RE)) {
|
|
217
|
+
hits.push({
|
|
218
|
+
ruleId: "22",
|
|
219
|
+
title: cfg22.title,
|
|
220
|
+
kind: "correct",
|
|
221
|
+
reason: "用户消息含确定性批评形态(规则 22②/手册 5b-2):用户批评不构成任何授权——被批评后应停、按归因四问产出三件套、等指令;禁止因批评启动任何变更"
|
|
222
|
+
});
|
|
223
|
+
} else if (CRITICISM_WEAK_RE.test(critText) && !isQuoteOrParaphraseContext(critText, CRITICISM_WEAK_RE)) {
|
|
224
|
+
hits.push({
|
|
225
|
+
ruleId: "22",
|
|
226
|
+
title: cfg22.title,
|
|
227
|
+
kind: "self-certify",
|
|
228
|
+
mode: "criticism",
|
|
229
|
+
reason: "用户消息疑似批评/质问形态(规则 22②/手册 5b-2,待裁决):若确属批评——不构成任何授权,被批评后应停、按归因四问产出三件套、等指令;禁止因批评启动任何变更"
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
192
234
|
// 规则 31(2026-09-01 用户拍板):同回合同一只读工具 ≥3 次且思维链无验证目标语 → 提示(B 级:留痕+注入)
|
|
193
|
-
const rule31Cfg = byId.get("31");
|
|
194
|
-
if (rule31Cfg) {
|
|
235
|
+
const rule31Cfg = byId.get("31"); if (rule31Cfg) {
|
|
195
236
|
const counts = new Map();
|
|
196
237
|
const pending = session.turn?.pendingToolCalls;
|
|
197
238
|
if (pending && typeof pending.values === "function") {
|
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
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
// - --sync-profile:发布成功后自动更新 profiles/<profile>/pnpm-workspace.yaml 豁免名单
|
|
7
7
|
// (备份原文件)+ 运行全量装配审计(写 .dsh 需以 danger-full-access 运行本脚本)
|
|
8
8
|
// 前置:npm 已认证、gh 已认证、git 已配置。
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// 步骤:版本 bump -> 测试 -> npm pack -> npm publish ->
|
|
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
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "node:fs";
|
|
15
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, statSync } from "node:fs";
|
|
15
16
|
import { dirname, join, relative, resolve } from "node:path";
|
|
16
17
|
import { fileURLToPath } from "node:url";
|
|
17
18
|
|
|
@@ -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/)
|
|
@@ -219,7 +249,27 @@ const untrackedList = quiet(`cd /d "${repoRoot}" && git status --porcelain -- ${
|
|
|
219
249
|
if (untrackedList.length > 0) {
|
|
220
250
|
console.log(`(未跟踪文件 ${untrackedList.length} 个将一并提交:${untrackedList.slice(0, 6).join(", ")}${untrackedList.length > 6 ? " 等" : ""})`);
|
|
221
251
|
}
|
|
222
|
-
|
|
252
|
+
// 2026-09-02:提交物个人标识/本机路径扫描(git 提交侧门禁;与 publish-aptitude-check.mjs PERSONAL_RE 同源)
|
|
253
|
+
// 教训 8105d3e:logs/.analysis-tmp 手册草稿曾随 add -A 进入公开库——npm 侧有门禁,git 侧此前没有。
|
|
254
|
+
const PERSONAL_RE = /jilian|季涟|D:\\DeepSeek|D:\/DeepSeek|@qq\.com|@163\.com|@outlook\.com|私人注释|个人标识/;
|
|
255
|
+
function scanStagedPersonal(repoRoot, stageSpec) {
|
|
256
|
+
const files = quiet(`cd /d "${repoRoot}" && git diff --cached --name-only -- ${stageSpec || "."}`)
|
|
257
|
+
.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
258
|
+
const bad = [];
|
|
259
|
+
for (const f of files) {
|
|
260
|
+
const p = join(repoRoot, f);
|
|
261
|
+
if (!existsSync(p) || statSync(p).size > 2 * 1024 * 1024) continue;
|
|
262
|
+
const text = readFileSync(p, "utf8");
|
|
263
|
+
// 豁免:LICENSE/版权行=MIT 许可要求版权声明(作者署名是公开出版物必要内容,非泄露)
|
|
264
|
+
if (/^LICENSE/i.test(path.basename(f))) continue;
|
|
265
|
+
if (/Copyright\s*\(c\)/i.test(text)) continue;
|
|
266
|
+
if (PERSONAL_RE.test(text)) bad.push(f);
|
|
267
|
+
}
|
|
268
|
+
if (bad.length) fail(`提交物含个人标识/本机路径(PERSONAL_RE 命中 ${bad.length} 个文件):${bad.slice(0, 4).join(", ")};请脱敏后重试`);
|
|
269
|
+
}
|
|
270
|
+
run(`cd /d "${repoRoot}" && ${proxyPrefix()}git add -A -- ${stageSpec}`);
|
|
271
|
+
scanStagedPersonal(repoRoot, stageSpec);
|
|
272
|
+
run(`cd /d "${repoRoot}" && git -c core.autocrlf=false commit -m "release: ${name} v${nextVer}" || exit 0`);
|
|
223
273
|
run(`cd /d "${repoRoot}" && ${proxyPrefix()}git push "${pushUrl}" HEAD`);
|
|
224
274
|
|
|
225
275
|
// ── 6. GitHub Release(带正式 tgz asset,规则 26;清单标记 skipRelease 的包不建)─────
|