dsh-rule-engine 0.5.4 → 0.5.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-rule-engine",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "DSH 规则执行引擎 v3:容器解析 AGENTS.md + 理解器 + 匹配机 + 执行框架",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -0,0 +1,55 @@
1
+ // rules-health.mjs - 规则触发率统计(阶段 2"数据驱动精简"的基础工具,2026-08-24)
2
+ // 用法:node scripts/rules-health.mjs [--top 10]
3
+ // 数据源:DSH_HOME/rule-engine.log.jsonl(引擎审计日志——四类审计自 2026-08-24 起)
4
+ // 输出:① 各规则触发统计(按 total 排序)② 零命中清单(对照 rule-understanding.json 规则全集)
5
+ // ③ 样本标注:总量 < MIN_SAMPLE 时提示"样本不足,暂不据此删/并/浓缩规则"。
6
+ import { readFileSync, existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import os from "node:os";
9
+
10
+ const DSH_HOME = process.env.DSH_HOME || join(os.homedir(), ".dsh");
11
+ const LOG = join(DSH_HOME, "rule-engine.log.jsonl");
12
+ const UNDERSTANDING = join(DSH_HOME, "rule-understanding.json");
13
+ const MIN_SAMPLE = 200;
14
+ const TOP = Number((process.argv.find((a) => a.startsWith("--top=")) || "").split("=")[1]) || 10;
15
+
16
+ if (!existsSync(LOG)) {
17
+ console.log(`NO LOG: ${LOG}`);
18
+ process.exit(0);
19
+ }
20
+
21
+ const stats = new Map(); // ruleId -> Map(kind -> count)
22
+ let total = 0;
23
+ for (const line of readFileSync(LOG, "utf8").split("\n")) {
24
+ if (!line.trim()) continue;
25
+ let e;
26
+ try { e = JSON.parse(line); } catch { continue; }
27
+ if (!e || !e.kind || !e.rule) continue;
28
+ total++;
29
+ const byKind = stats.get(String(e.rule)) || new Map();
30
+ byKind.set(e.kind, (byKind.get(e.kind) || 0) + 1);
31
+ stats.set(String(e.rule), byKind);
32
+ }
33
+
34
+ // 规则全集(对照零命中)
35
+ let ruleIds = new Set();
36
+ try {
37
+ const u = JSON.parse(readFileSync(UNDERSTANDING, "utf8"));
38
+ ruleIds = new Set((u.rules || []).map((r) => String(r.ruleId)));
39
+ } catch { /* 理解产物缺失时只统计日志内规则 */ }
40
+
41
+ const rows = [...stats.entries()]
42
+ .map(([rule, byKind]) => ({ rule, total: [...byKind.values()].reduce((a, b) => a + b, 0), byKind }))
43
+ .sort((a, b) => b.total - a.total);
44
+
45
+ console.log(`规则触发率统计(样本总量 ${total}${total < MIN_SAMPLE ? `,⚠ 样本不足 ${MIN_SAMPLE}——暂不据此删/并规则` : ""})`);
46
+ console.log("规则 触发数 分类明细");
47
+ for (const { rule, total, byKind } of rows.slice(0, TOP)) {
48
+ const detail = [...byKind.entries()].map(([k, n]) => `${k}:${n}`).join(" ");
49
+ console.log(`${String(rule).padEnd(10)} ${String(total).padEnd(7)} ${detail}`);
50
+ }
51
+ const zero = [...ruleIds].filter((id) => !stats.has(id));
52
+ if (zero.length > 0) {
53
+ console.log(`\n零命中规则(${zero.length}):${zero.join(", ")}`);
54
+ console.log("注:零命中 ≠ 无用(条件触发型低频规则正常);仅作体检参考,勿据单次样本删规则。");
55
+ }