dsh-rule-engine 0.5.9 → 0.5.11
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 +86 -7
- package/lib/core/authorization.js +64 -19
- package/lib/core/guard-core.js +100 -13
- package/lib/core/hotwords.js +86 -0
- package/lib/core/intent.js +51 -14
- package/lib/core/judge.js +72 -0
- package/lib/core/lexicon.js +72 -0
- package/lib/core/llm-intent.js +10 -2
- package/lib/core/llm-understander.js +15 -0
- package/lib/core/patterns.js +162 -12
- package/lib/core/text-detect.js +67 -11
- package/lib/core/tool-catalog.js +3 -0
- package/lib/core/understander.js +1 -1
- package/lib/core/version-guard.js +6 -3
- package/lib/index.js +235 -50
- package/package.json +1 -1
- package/scripts/health-audit.mjs +7 -1
- package/scripts/probe-home.mjs +3 -0
- package/scripts/probe-intent.mjs +14 -0
- package/scripts/verify-all.mjs +44 -2
- package/scripts/verify-guard-live.mjs +30 -0
- package/scripts/verify-v474.mjs +14 -0
package/scripts/health-audit.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from "node:url";
|
|
|
11
11
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
12
|
const auditPath = join(process.env.DSH_HOME || join(process.env.USERPROFILE || "", ".dsh"), "rule-engine.log.jsonl");
|
|
13
13
|
const now = Date.now();
|
|
14
|
-
const cuts = { intentFail: 0, intentOk: 0, judgePass: 0, judgeFalse: 0, judgeUnavail: 0, verifyGap: 0, injectSkip: 0, sourceSkip: 0 };
|
|
14
|
+
const cuts = { intentFail: 0, intentOk: 0, judgePass: 0, judgeFalse: 0, judgeUnavail: 0, verifyGap: 0, injectSkip: 0, sourceSkip: 0, denyTotal: 0, labelIncorrect: 0, labelCorrect: 0, errorHint: 0 };
|
|
15
15
|
|
|
16
16
|
try {
|
|
17
17
|
for (const line of readFileSync(auditPath, "utf8").split("\n")) {
|
|
@@ -24,6 +24,10 @@ try {
|
|
|
24
24
|
if (line.includes('"kind":"verify-gap"')) cuts.verifyGap++;
|
|
25
25
|
if (line.includes('"kind":"inject-skip"')) cuts.injectSkip++;
|
|
26
26
|
if (line.includes('"kind":"source-skip"')) cuts.sourceSkip++;
|
|
27
|
+
if (line.includes('"kind":"deny"')) cuts.denyTotal++;
|
|
28
|
+
if (line.includes('"kind":"task-label"') && line.includes("incorrect")) cuts.labelIncorrect++;
|
|
29
|
+
if (line.includes('"kind":"task-label"') && line.includes("correct")) cuts.labelCorrect++;
|
|
30
|
+
if (line.includes('"kind":"error-hint"')) cuts.errorHint++;
|
|
27
31
|
}
|
|
28
32
|
} catch {
|
|
29
33
|
// 无日志文件:一切 0
|
|
@@ -49,6 +53,8 @@ console.log("== 健康审计(近 24h)==");
|
|
|
49
53
|
console.log(`intent-llm 失败降级 ${cuts.intentFail} 条 / 成功 ${cuts.intentOk} 条`);
|
|
50
54
|
console.log(`judge-pass ${cuts.judgePass} 条 / judge-false ${cuts.judgeFalse} 条 / judge-unavailable ${cuts.judgeUnavail} 条`);
|
|
51
55
|
console.log(`verify-gap ${cuts.verifyGap} 条 / inject-skip ${cuts.injectSkip} 条 / source-skip ${cuts.sourceSkip} 条`);
|
|
56
|
+
console.log(`误判打标(建议4):deny 总数 ${cuts.denyTotal} 条 → label incorrect ${cuts.labelIncorrect} 条 / correct ${cuts.labelCorrect} 条${cuts.denyTotal > 0 && cuts.labelIncorrect > 0 ? `(incorrect 占比 ${(cuts.labelIncorrect / cuts.denyTotal * 100).toFixed(1)}%)` : ""}——词表迭代量化依据`);
|
|
57
|
+
console.log(`已知坑召回(建议5):error-hint ${cuts.errorHint} 条`);
|
|
52
58
|
console.log(`接线交叉:${EXPORTS.length} 个关键导出 → 疑似未接线 ${orphans.length ? orphans.join("、") : "0 个"}`);
|
|
53
59
|
if (cuts.judgePass + cuts.judgeFalse === 0) {
|
|
54
60
|
console.log("⚠️ 问题:近 24h 无真实判例——裁决器无运行证据(需实弹)");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// probe-intent.mjs - 测"落"字为何被判问句(用户明确执行却拦)
|
|
2
|
+
import { parseUserIntents, shouldDenyMutation } from "../lib/core/intent.js";
|
|
3
|
+
const texts = [
|
|
4
|
+
"2、连同上面这条一起落",
|
|
5
|
+
"连上面这条一起落",
|
|
6
|
+
"落",
|
|
7
|
+
"全部一起落",
|
|
8
|
+
"认可全部link不装npm形态",
|
|
9
|
+
"1、认可形态 2、连上面这条一起落"
|
|
10
|
+
];
|
|
11
|
+
for (const t of texts) {
|
|
12
|
+
const r = parseUserIntents(t);
|
|
13
|
+
console.log(`"${t}" → hasExecute=${r.hasExecute} hasQuestion=${r.hasQuestion} types=${r.clauses.map((c) => c.type).join(",")}`);
|
|
14
|
+
}
|
package/scripts/verify-all.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
// verify-all.mjs - 交付前体检报告(2026-08-26,对齐官方 docs/testing.zh.md 分层验证;0.5.9
|
|
2
|
-
//
|
|
1
|
+
// verify-all.mjs - 交付前体检报告(2026-08-26,对齐官方 docs/testing.zh.md 分层验证;0.5.9 增第⑤层;0.5.11 增第⑥层)。
|
|
2
|
+
// 六层:① 语法(lib 全部 .js node --check)
|
|
3
3
|
// ② 单元(test/run-all.mjs——单元+机制层,不含 e2e)
|
|
4
4
|
// ③ 组合冒烟(test/loader-smoke.e2e.mjs——真实接线 + 外部世界断言)
|
|
5
5
|
// ④ 真实判例(外部世界:近 24h 台账中 judge-pass/judge-false 记录数;
|
|
6
6
|
// 0 条 = WARN 提示需实弹,≥1 条 = 有真实裁决证据——读文件,不是自我报告)
|
|
7
7
|
// ⑤ 工具箱覆盖(scripts/check-tool-coverage.mjs——官方 tool-catalog 全集 vs 分类表,
|
|
8
8
|
// 任何 unknown = 未知工具首调处置会拦用户 → 红色(run_code 事故同类,K-01/K-06 门禁))
|
|
9
|
+
// ⑥ 变更工具守卫链覆盖(test/guardchain-coverage.test.mjs——规则 24④ 机器执行,
|
|
10
|
+
// 跨工具一致性测试:每个写/删/移工具在问句回合+无授权场景跑真实 guardDecision,
|
|
11
|
+
// 断言被拦或属豁免面——静默逃逸 = 红;工具名非安全边界,裁决链才是(K-04))
|
|
9
12
|
// 任一 ❌ → exit 1;⚠️(WARN)不阻塞但必须明示。
|
|
10
13
|
// 注意:子进程输出捕获需完整权限运行(受限模式 EPERM)。
|
|
11
14
|
import { execFileSync } from "node:child_process";
|
|
@@ -58,6 +61,45 @@ step("组合冒烟(loader-smoke,真实接线+外部世界断言)", process
|
|
|
58
61
|
// ── ⑤ 工具箱覆盖(0.5.9 门禁) ──
|
|
59
62
|
step("工具箱覆盖(官方 tool-catalog vs 分类表,缺失即红)", process.execPath, [join(root, "scripts", "check-tool-coverage.mjs")]);
|
|
60
63
|
|
|
64
|
+
// ── ⑥ 变更工具守卫链覆盖(0.5.11 规则 24④ 跨工具一致性门禁) ──
|
|
65
|
+
step("变更工具守卫链覆盖(写/删/移工具问句回合不静默逃逸)", process.execPath, [join(root, "test", "guardchain-coverage.test.mjs")]);
|
|
66
|
+
|
|
67
|
+
// ── ⑦ 关联一致性门禁(0.5.11,用户定稿:验收 = 改动关联产物全做一遍,机器强制) ──
|
|
68
|
+
// ① test/*.test.mjs 全部被 run-all.mjs 收录(新增测试漏收录 = 红——guardchain 曾漏收)
|
|
69
|
+
// ② README 徽章版本 = package.json 版本(版本成对)
|
|
70
|
+
// ③ 文档:README/手册提及的"引擎已做"核心行为在 lib 中有落点(关键词级抽样,防"只写文档没实现")
|
|
71
|
+
let assocFail = [];
|
|
72
|
+
{
|
|
73
|
+
try {
|
|
74
|
+
const runAllText = readFileSync(join(root, "test", "run-all.mjs"), "utf8");
|
|
75
|
+
const testFiles = readdirSync(join(root, "test")).filter((f) => f.endsWith(".test.mjs"));
|
|
76
|
+
for (const f of testFiles) {
|
|
77
|
+
if (!runAllText.includes(f)) assocFail.push(`测试未收录 run-all.mjs:${f}`);
|
|
78
|
+
}
|
|
79
|
+
} catch (e) { assocFail.push(`run-all 收录检查失败:${e.message}`); }
|
|
80
|
+
try {
|
|
81
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
82
|
+
const readme = readFileSync(join(root, "README.md"), "utf8");
|
|
83
|
+
// 版本成对支持 -pre/-rc 后缀(工作区未发布代号如 0.5.11-pre;badge URL 编码 --pre)
|
|
84
|
+
const badge = readme.match(/version-([0-9.]+)(--pre|-pre)?-blue/);
|
|
85
|
+
const badgeVer = badge ? badge[1] + (badge[2] ? "-pre" : "") : null;
|
|
86
|
+
if (!badgeVer || badgeVer !== pkg.version) assocFail.push(`版本成对失败:package.json=${pkg.version} vs README badge=${badgeVer || "?"}`);
|
|
87
|
+
} catch (e) { assocFail.push(`版本成对检查失败:${e.message}`); }
|
|
88
|
+
// ③ 关键词落点:引擎宣称的六大能力,lib 中各有引用(防"文档说了、代码没做")
|
|
89
|
+
try {
|
|
90
|
+
const libText = readdirSync(join(root, "lib/core")).join(",");
|
|
91
|
+
const mustHave = ["tool-catalog", "lexicon", "guard-core", "intent", "authorization"];
|
|
92
|
+
for (const m of mustHave) {
|
|
93
|
+
if (!libText.includes(m + ".js")) assocFail.push(`核心落点缺失:lib/core/${m}.js`);
|
|
94
|
+
}
|
|
95
|
+
} catch (e) { assocFail.push(`核心落点检查失败:${e.message}`); }
|
|
96
|
+
}
|
|
97
|
+
if (assocFail.length > 0) {
|
|
98
|
+
for (const a of assocFail) lines.push(`❌ 关联一致性:${a}`);
|
|
99
|
+
} else {
|
|
100
|
+
lines.push("✅ 关联一致性(测试全部收录 / 版本成对 / 核心能力有落点)");
|
|
101
|
+
}
|
|
102
|
+
|
|
61
103
|
// ── ④ 真实判例(外部世界) ──
|
|
62
104
|
let judgeReal = 0;
|
|
63
105
|
const auditPath = join(process.env.DSH_HOME || join(process.env.USERPROFILE || "", ".dsh"), "rule-engine.log.jsonl");
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// verify-guard-live.mjs — 0.5.11 运行态实弹验证(验收⑥):版本守卫端到端行为
|
|
2
|
+
// 场景:edit 一个版本化文件(SKILL.md 类),old 唯一、单行整句重写 → 应放行;多行真覆盖 → 应拦
|
|
3
|
+
import { validateEditedFile } from "../lib/core/version-guard.js";
|
|
4
|
+
|
|
5
|
+
// 模拟:current 含 3 行,old="第二行旧句"唯一出现,new="第二行新句"(单行整句重写)
|
|
6
|
+
const current = "第一行\n第二行旧句\n第三行\n";
|
|
7
|
+
const simulated = "第一行\n第二行新句\n第三行\n";
|
|
8
|
+
const old = "第二行旧句";
|
|
9
|
+
const newS = "第二行新句";
|
|
10
|
+
|
|
11
|
+
// 唯一性(模拟工具已确认 old 唯一)
|
|
12
|
+
const unique = current.split(old).length - 1 === 1;
|
|
13
|
+
console.log("old 唯一匹配:", unique, unique ? "✅" : "❌");
|
|
14
|
+
|
|
15
|
+
const res1 = validateEditedFile(current, simulated, old, newS, unique);
|
|
16
|
+
console.log("单行整句重写:", res1.ok ? "✅ 放行" : "❌ 拦截(" + (res1.errors || []).join(";") + ")");
|
|
17
|
+
|
|
18
|
+
// 多行真覆盖(old 两行被无关内容替换)
|
|
19
|
+
const current2 = "line1\nline2\nline3\n";
|
|
20
|
+
const sim2 = "line1\nREPLACED\n";
|
|
21
|
+
const res2 = validateEditedFile(current2, sim2, "line2\nline3", "REPLACED", true);
|
|
22
|
+
console.log("多行真覆盖:", res2.ok ? "❌ 放行(危险!)" : "✅ 拦截");
|
|
23
|
+
|
|
24
|
+
// 删除/缩短(old 子串 new)仍放行 = 正常删行
|
|
25
|
+
const res3 = validateEditedFile(current2, "line1\n", "line2\nline3", "", true);
|
|
26
|
+
console.log("删除行(new 为空=缩短):", res3.ok ? "✅ 放行(删除属正常编辑)" : "❌ 拦截");
|
|
27
|
+
|
|
28
|
+
const pass = res1.ok && !res2.ok && res3.ok;
|
|
29
|
+
console.log("\n=== " + (pass ? "LIVE VERIFY PASS" : "LIVE VERIFY FAIL") + " ===");
|
|
30
|
+
process.exit(pass ? 0 : 1);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// verify-v474.mjs - v4.74 记录项引擎在位抽查(分点切分/规则2双检/词表跑)
|
|
2
|
+
import { parseUserIntents } from "../lib/core/intent.js";
|
|
3
|
+
import { ACTION_WORDS_RE } from "../lib/core/lexicon.js";
|
|
4
|
+
import { EVIDENCE_MARK_RE, TIME_WORDS } from "../lib/core/patterns.js";
|
|
5
|
+
|
|
6
|
+
// ① 分点切分(标点后编号——用户"1、问2、问3、跑"同行)
|
|
7
|
+
const mixed = parseUserIntents("1、为什么预算会满?2、检查时间规则?3、跑");
|
|
8
|
+
console.log("分点(同行标点):", mixed.hasExecute && mixed.clauses[2].type === "execute" ? "✅" : "❌");
|
|
9
|
+
// ② 词表补跑
|
|
10
|
+
console.log("词表含跑:", ACTION_WORDS_RE.test("跑") ? "✅" : "❌");
|
|
11
|
+
// ③ 规则2双检:EVIDENCE_MARK_RE 存在、TIME_WORDS 具体词
|
|
12
|
+
console.log("规则2证据词表:", EVIDENCE_MARK_RE.test("日志 ts=2026-08-27T15:21:05Z") ? "✅" : "❌");
|
|
13
|
+
console.log("规则2具体词(昨天):", TIME_WORDS.test("昨天") ? "✅" : "❌");
|
|
14
|
+
console.log("规则2模糊词(之前):", TIME_WORDS.test("之前") ? "❌(应不命中)" : "✅(不命中)");
|