dsh-plugin-compat-check 0.1.0 → 0.1.2
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/LICENSE +21 -0
- package/README.md +21 -57
- package/index.js +93 -155
- package/kernel/lib/baseline.mjs +110 -34
- package/kernel/lib/contracts.mjs +493 -0
- package/kernel/lib/declarations.mjs +50 -0
- package/kernel/lib/extract-worker.mjs +10 -0
- package/kernel/lib/extract.mjs +235 -1515
- package/kernel/lib/flow-values.mjs +53 -0
- package/kernel/lib/observation.mjs +182 -0
- package/kernel/lib/report-service.mjs +55 -0
- package/kernel/lib/report.mjs +237 -0
- package/kernel/lib/source-analysis.mjs +1913 -0
- package/kernel/lib/source-graph.mjs +297 -0
- package/kernel/lib/state-store.mjs +126 -0
- package/kernel/lib/syntax.mjs +73 -0
- package/kernel/lib/verdict.mjs +492 -587
- package/kernel/lib/version.mjs +2 -2
- package/kernel/lib/worker-analysis.mjs +82 -0
- package/kernel/rules/README.md +46 -58
- package/kernel/rules/severity.json +161 -35
- package/kernel/rules/usage-inventory.json +369 -61
- package/lib/client.js +523 -10
- package/lib/host-resources.js +92 -0
- package/lib/report-contract.js +494 -0
- package/package.json +14 -5
- package/scripts/prepublish-check.mjs +14 -9
- package/scripts/stage-contract.mjs +43 -0
- package/scripts/stage-kernel.mjs +9 -21
- package/skill/SKILL.md +40 -39
package/kernel/lib/baseline.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateBaseline } from "./contracts.mjs";
|
|
1
2
|
/**
|
|
2
3
|
* 基线:回答「**变了没有**」,以及变了多少、往哪个方向变。
|
|
3
4
|
*
|
|
@@ -14,14 +15,14 @@
|
|
|
14
15
|
*
|
|
15
16
|
* ## 两条硬约束
|
|
16
17
|
*
|
|
17
|
-
* 1. **只写自己的状态目录**(Plan.md §
|
|
18
|
+
* 1. **只写自己的状态目录**(docs/Plan.md §1 与 §3 第 4 条:插件严格只读之外的唯一例外)。
|
|
18
19
|
* 不联网、不外传 —— 「我装了哪些插件」是本机信息。
|
|
19
20
|
* 2. **绝不把「没变」当成「没问题」。** 基线只回答差分;结论仍然来自判定引擎。
|
|
20
21
|
* 两者混在一份输出里,会让人以为"没有变化"等于"兼容"。
|
|
21
22
|
*/
|
|
22
23
|
|
|
23
24
|
/** 基线的存储版本:结构变了就要能识别出旧基线,而不是读出一个错的差分 */
|
|
24
|
-
export const BASELINE_SCHEMA =
|
|
25
|
+
export const BASELINE_SCHEMA = 2;
|
|
25
26
|
|
|
26
27
|
const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
27
28
|
|
|
@@ -35,7 +36,7 @@ const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
|
35
36
|
* @param {object} verdict buildVerdicts 的返回值
|
|
36
37
|
* @param {string} dshVersion 当前 DSH 版本(它变了,方法级结论理应跟着变)
|
|
37
38
|
*/
|
|
38
|
-
export function snapshotForBaseline(verdict, dshVersion) {
|
|
39
|
+
export function snapshotForBaseline(verdict, dshVersion, identity = {}) {
|
|
39
40
|
const plugins = {};
|
|
40
41
|
const names = Object.keys(verdict.plugins || {}).sort();
|
|
41
42
|
for (const name of names) {
|
|
@@ -47,16 +48,28 @@ export function snapshotForBaseline(verdict, dshVersion) {
|
|
|
47
48
|
// 发现按稳定键排序:顺序不该影响「变了没有」
|
|
48
49
|
findings: (p.findings || [])
|
|
49
50
|
.map((f) => ({
|
|
50
|
-
key: f.service
|
|
51
|
+
key: f.service
|
|
52
|
+
? f.method
|
|
53
|
+
? `${f.service}.${f.method}`
|
|
54
|
+
: f.service
|
|
55
|
+
: "(declaration)",
|
|
51
56
|
category: f.category,
|
|
52
57
|
severity: f.severity,
|
|
53
58
|
certainty: f.certainty,
|
|
54
59
|
location: f.location ? `${f.location.file}:${f.location.line}` : null,
|
|
55
60
|
}))
|
|
56
|
-
.sort(
|
|
61
|
+
.sort(
|
|
62
|
+
(a, b) =>
|
|
63
|
+
a.key.localeCompare(b.key) || a.category.localeCompare(b.category),
|
|
64
|
+
),
|
|
57
65
|
};
|
|
58
66
|
}
|
|
59
|
-
return {
|
|
67
|
+
return {
|
|
68
|
+
schemaVersion: BASELINE_SCHEMA,
|
|
69
|
+
dshVersion: dshVersion ?? null,
|
|
70
|
+
identity: { mode: verdict.observation?.mode ?? "unspecified", ...identity },
|
|
71
|
+
plugins,
|
|
72
|
+
};
|
|
60
73
|
}
|
|
61
74
|
|
|
62
75
|
/** 结论档位的"好坏"序:用于判断某个变化是变好还是变差 */
|
|
@@ -86,24 +99,39 @@ export function diffAgainstBaseline(prev, next) {
|
|
|
86
99
|
removed: [],
|
|
87
100
|
worsened: [],
|
|
88
101
|
improved: [],
|
|
102
|
+
updated: [],
|
|
89
103
|
};
|
|
90
104
|
|
|
91
105
|
if (!prev || prev.schemaVersion !== BASELINE_SCHEMA) {
|
|
92
106
|
out.reason = prev
|
|
93
107
|
? `基线结构版本不匹配(存的 ${prev.schemaVersion},当前认 ${BASELINE_SCHEMA})`
|
|
94
|
-
:
|
|
108
|
+
: "还没有基线(这是第一次体检)";
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
JSON.stringify(prev.identity || {}) !== JSON.stringify(next.identity || {})
|
|
113
|
+
) {
|
|
114
|
+
out.reason =
|
|
115
|
+
"体检对象、采样方式或判定规则变了,不能将两次结果当成插件状态变化";
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
if (validateBaseline(prev).length || validateBaseline(next).length) {
|
|
119
|
+
out.reason = "基线内容结构或契约损坏,本次不做比较";
|
|
95
120
|
return out;
|
|
96
121
|
}
|
|
97
122
|
out.comparable = true;
|
|
98
123
|
out.dshVersionFrom = prev.dshVersion ?? null;
|
|
99
124
|
out.dshVersionTo = next.dshVersion ?? null;
|
|
100
|
-
out.dshVersionChanged =
|
|
125
|
+
out.dshVersionChanged =
|
|
126
|
+
(prev.dshVersion ?? null) !== (next.dshVersion ?? null);
|
|
101
127
|
|
|
102
128
|
const a = prev.plugins || {};
|
|
103
129
|
const b = next.plugins || {};
|
|
104
130
|
|
|
105
|
-
for (const name of Object.keys(a))
|
|
106
|
-
|
|
131
|
+
for (const name of Object.keys(a))
|
|
132
|
+
if (!has(b, name)) out.removed.push({ name, version: a[name].version });
|
|
133
|
+
for (const name of Object.keys(b))
|
|
134
|
+
if (!has(a, name)) out.added.push({ name, version: b[name].version });
|
|
107
135
|
|
|
108
136
|
for (const name of Object.keys(a)) {
|
|
109
137
|
if (!has(b, name)) continue;
|
|
@@ -118,8 +146,36 @@ export function diffAgainstBaseline(prev, next) {
|
|
|
118
146
|
|
|
119
147
|
const xk = new Set(x.findings.map((f) => `${f.key}|${f.category}`));
|
|
120
148
|
const yk = new Set(y.findings.map((f) => `${f.key}|${f.category}`));
|
|
121
|
-
const newFindings = y.findings.filter(
|
|
122
|
-
|
|
149
|
+
const newFindings = y.findings.filter(
|
|
150
|
+
(f) => !xk.has(`${f.key}|${f.category}`),
|
|
151
|
+
);
|
|
152
|
+
const goneFindings = x.findings.filter(
|
|
153
|
+
(f) => !yk.has(`${f.key}|${f.category}`),
|
|
154
|
+
);
|
|
155
|
+
const severityRank = { degraded: 0, "feature-dead": 1, fatal: 2 };
|
|
156
|
+
const certaintyRank = { suspected: 0, certain: 1 };
|
|
157
|
+
const changes = y.findings
|
|
158
|
+
.map((f) => ({
|
|
159
|
+
from: x.findings.find(
|
|
160
|
+
(g) => g.key === f.key && g.category === f.category,
|
|
161
|
+
),
|
|
162
|
+
to: f,
|
|
163
|
+
}))
|
|
164
|
+
.filter(
|
|
165
|
+
(c) =>
|
|
166
|
+
c.from &&
|
|
167
|
+
(c.from.severity !== c.to.severity ||
|
|
168
|
+
c.from.certainty !== c.to.certainty ||
|
|
169
|
+
c.from.location !== c.to.location),
|
|
170
|
+
);
|
|
171
|
+
const evidenceWorse = changes.some(
|
|
172
|
+
(c) =>
|
|
173
|
+
severityRank[c.to.severity] > severityRank[c.from.severity] ||
|
|
174
|
+
certaintyRank[c.to.certainty] > certaintyRank[c.from.certainty],
|
|
175
|
+
);
|
|
176
|
+
const evidenceBetter = changes.some(
|
|
177
|
+
(c) => severityRank[c.to.severity] < severityRank[c.from.severity],
|
|
178
|
+
);
|
|
123
179
|
|
|
124
180
|
const entry = {
|
|
125
181
|
name,
|
|
@@ -130,33 +186,40 @@ export function diffAgainstBaseline(prev, next) {
|
|
|
130
186
|
verdictTo: y.verdict,
|
|
131
187
|
newFindings,
|
|
132
188
|
goneFindings,
|
|
189
|
+
changedFindings: changes,
|
|
133
190
|
};
|
|
134
191
|
|
|
135
192
|
// 变差:档位下降,或**新增了发现**(档位可能没动,例如两条降级变三条)。
|
|
136
193
|
// 注意 `newFindings.length` 优先于档位判断 —— 档位相同但多了发现也是变差。
|
|
137
|
-
if (worseVerdict || newFindings.length > 0) {
|
|
194
|
+
if (worseVerdict || newFindings.length > 0 || evidenceWorse) {
|
|
138
195
|
entry.reason = worseVerdict
|
|
139
196
|
? `结论从「${x.verdict}」变为「${y.verdict}」`
|
|
140
|
-
:
|
|
197
|
+
: evidenceWorse
|
|
198
|
+
? "已有发现的严重度或证据确定性提高了"
|
|
199
|
+
: `新增 ${newFindings.length} 条发现(档位未变)`;
|
|
141
200
|
out.worsened.push(entry);
|
|
142
201
|
continue;
|
|
143
202
|
}
|
|
144
|
-
if (betterVerdict || goneFindings.length > 0) {
|
|
203
|
+
if (betterVerdict || goneFindings.length > 0 || evidenceBetter) {
|
|
145
204
|
entry.reason = betterVerdict
|
|
146
205
|
? `结论从「${x.verdict}」变为「${y.verdict}」`
|
|
147
|
-
:
|
|
206
|
+
: evidenceBetter
|
|
207
|
+
? "已有发现的严重度降低了"
|
|
208
|
+
: `少了 ${goneFindings.length} 条发现(档位未变)`;
|
|
148
209
|
out.improved.push(entry);
|
|
149
210
|
continue;
|
|
150
211
|
}
|
|
151
212
|
// 什么都没变,但版本动了 —— 这也是一条要说的事
|
|
152
|
-
if (versionChanged) {
|
|
153
|
-
entry.reason =
|
|
154
|
-
|
|
213
|
+
if (versionChanged || changes.length) {
|
|
214
|
+
entry.reason = versionChanged
|
|
215
|
+
? "版本变了,但结论未变"
|
|
216
|
+
: "发现的位置或证据改变了";
|
|
217
|
+
out.updated.push(entry);
|
|
155
218
|
}
|
|
156
219
|
}
|
|
157
220
|
|
|
158
221
|
// 稳定排序:角标与列表的顺序不该随对象键顺序变化
|
|
159
|
-
for (const k of [
|
|
222
|
+
for (const k of ["added", "removed", "worsened", "improved", "updated"]) {
|
|
160
223
|
out[k].sort((p, q) => p.name.localeCompare(q.name));
|
|
161
224
|
}
|
|
162
225
|
return out;
|
|
@@ -165,7 +228,7 @@ export function diffAgainstBaseline(prev, next) {
|
|
|
165
228
|
/**
|
|
166
229
|
* 角标该显示什么。
|
|
167
230
|
*
|
|
168
|
-
* 规则:**任何变化都打**,并标明方向(Plan.md §2
|
|
231
|
+
* 规则:**任何变化都打**,并标明方向(docs/Plan.md §2.2)。
|
|
169
232
|
* 只有"变差"才亮会漏掉"变好" —— 而"某个插件被修好了、你可以放心留着了"
|
|
170
233
|
* 同样是用户需要知道的事。
|
|
171
234
|
*
|
|
@@ -174,19 +237,27 @@ export function diffAgainstBaseline(prev, next) {
|
|
|
174
237
|
export function badgeFor(diff) {
|
|
175
238
|
if (!diff || !diff.comparable) {
|
|
176
239
|
// 没有基线不是"没变化" —— 它意味着**还比不了**。角标不打,但要说清为什么。
|
|
177
|
-
return {
|
|
240
|
+
return {
|
|
241
|
+
show: false,
|
|
242
|
+
text: null,
|
|
243
|
+
direction: null,
|
|
244
|
+
detail: diff ? diff.reason : "没有基线",
|
|
245
|
+
};
|
|
178
246
|
}
|
|
179
247
|
const w = diff.worsened.length;
|
|
180
248
|
const b = diff.improved.length;
|
|
181
249
|
const add = diff.added.length;
|
|
182
250
|
const rm = diff.removed.length;
|
|
251
|
+
const updated = (diff.updated || []).length;
|
|
183
252
|
|
|
184
|
-
if (w === 0 && b === 0 && add === 0 && rm === 0) {
|
|
253
|
+
if (w === 0 && b === 0 && add === 0 && rm === 0 && updated === 0) {
|
|
185
254
|
return {
|
|
186
|
-
show: false,
|
|
255
|
+
show: false,
|
|
256
|
+
text: null,
|
|
257
|
+
direction: "neutral",
|
|
187
258
|
detail: diff.dshVersionChanged
|
|
188
|
-
? `DSH 从 ${diff.dshVersionFrom ??
|
|
189
|
-
:
|
|
259
|
+
? `DSH 从 ${diff.dshVersionFrom ?? "?"} 变为 ${diff.dshVersionTo ?? "?"},但所有插件的结论都没变`
|
|
260
|
+
: "与上次相比没有任何变化",
|
|
190
261
|
};
|
|
191
262
|
}
|
|
192
263
|
|
|
@@ -195,16 +266,19 @@ export function badgeFor(diff) {
|
|
|
195
266
|
if (b) bits.push(`变好 ${b}`);
|
|
196
267
|
if (add) bits.push(`新增 ${add}`);
|
|
197
268
|
if (rm) bits.push(`移除 ${rm}`);
|
|
198
|
-
|
|
199
|
-
const
|
|
269
|
+
if (updated) bits.push(`更新 ${updated}`);
|
|
270
|
+
const direction =
|
|
271
|
+
w > 0 && b > 0 ? "mixed" : w > 0 ? "worse" : b > 0 ? "better" : "neutral";
|
|
272
|
+
const arrow =
|
|
273
|
+
direction === "worse" ? "↓" : direction === "better" ? "↑" : "•";
|
|
200
274
|
|
|
201
275
|
return {
|
|
202
276
|
show: true,
|
|
203
|
-
text: `${arrow} ${bits.join(
|
|
277
|
+
text: `${arrow} ${bits.join(" · ")}`,
|
|
204
278
|
direction,
|
|
205
279
|
detail: diff.dshVersionChanged
|
|
206
|
-
? `DSH 版本也从 ${diff.dshVersionFrom ??
|
|
207
|
-
:
|
|
280
|
+
? `DSH 版本也从 ${diff.dshVersionFrom ?? "?"} 变成了 ${diff.dshVersionTo ?? "?"} —— 这常常是上面这些变化的原因`
|
|
281
|
+
: "DSH 版本没变",
|
|
208
282
|
};
|
|
209
283
|
}
|
|
210
284
|
|
|
@@ -215,13 +289,14 @@ export function badgeFor(diff) {
|
|
|
215
289
|
* 而坏消息被"另外还发现两个插件变好了"稀释掉,就等于没说。
|
|
216
290
|
*/
|
|
217
291
|
export function describeDiff(diff) {
|
|
218
|
-
if (!diff || !diff.comparable) return diff ? diff.reason :
|
|
292
|
+
if (!diff || !diff.comparable) return diff ? diff.reason : "没有基线";
|
|
219
293
|
const badge = badgeFor(diff);
|
|
220
294
|
if (!badge.show) return badge.detail;
|
|
221
295
|
const L = [badge.text];
|
|
222
296
|
for (const e of diff.worsened) {
|
|
223
297
|
L.push(` ↓ ${e.name}@${e.versionFrom}→${e.versionTo}:${e.reason}`);
|
|
224
|
-
for (const f of e.newFindings)
|
|
298
|
+
for (const f of e.newFindings)
|
|
299
|
+
L.push(` 新增:${f.key} @ ${f.location || "—"}`);
|
|
225
300
|
}
|
|
226
301
|
for (const e of diff.improved) {
|
|
227
302
|
L.push(` ↑ ${e.name}:${e.reason}`);
|
|
@@ -229,5 +304,6 @@ export function describeDiff(diff) {
|
|
|
229
304
|
}
|
|
230
305
|
for (const e of diff.added) L.push(` + ${e.name}@${e.version}`);
|
|
231
306
|
for (const e of diff.removed) L.push(` − ${e.name}@${e.version}`);
|
|
232
|
-
|
|
307
|
+
for (const e of diff.updated || []) L.push(` • ${e.name}:${e.reason}`);
|
|
308
|
+
return L.join("\n");
|
|
233
309
|
}
|