dsh-plugin-compat-check 0.1.5 → 0.1.7
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 +17 -28
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/index.js +1295 -550
- package/kernel/lib/api-semantics.mjs +223 -0
- package/kernel/lib/author-ci-budget.mjs +25 -0
- package/kernel/lib/author-ci-output.mjs +22 -0
- package/kernel/lib/author-ci-policy.mjs +6 -1
- package/kernel/lib/baseline.mjs +109 -38
- package/kernel/lib/catalog.mjs +11 -3
- package/kernel/lib/contracts.mjs +16 -1
- package/kernel/lib/execution-environment.mjs +52 -0
- package/kernel/lib/extract.mjs +57 -13
- package/kernel/lib/finding-guidance.mjs +6 -0
- package/kernel/lib/finding-review-store.mjs +11 -23
- package/kernel/lib/issue-report.mjs +2 -1
- package/kernel/lib/patch-mounts.mjs +232 -0
- package/kernel/lib/report-identity-worker.mjs +47 -26
- package/kernel/lib/report-identity.mjs +4 -3
- package/kernel/lib/report-service.mjs +118 -38
- package/kernel/lib/report.mjs +13 -17
- package/kernel/lib/source-analysis.mjs +258 -42
- package/kernel/lib/source-graph.mjs +353 -75
- package/kernel/lib/state-lock.mjs +206 -0
- package/kernel/lib/state-store.mjs +41 -30
- package/kernel/lib/static-input.mjs +111 -0
- package/kernel/lib/syntax.mjs +242 -20
- package/kernel/lib/upgrade-contract.mjs +1 -0
- package/kernel/lib/upgrade-input.mjs +9 -4
- package/kernel/lib/upgrade-preview.mjs +11 -15
- package/kernel/lib/upgrade-worker.mjs +10 -1
- package/kernel/lib/verdict.mjs +28 -5
- package/kernel/lib/version.mjs +2 -34
- package/kernel/lib/worker-analysis.mjs +74 -3
- package/kernel/rules/README.md +30 -208
- package/kernel/rules/dsh-0.2.1-rc.3-review.json +63 -0
- package/kernel/rules/dsh-api-catalog-0.2.1-rc.3.json +743 -0
- package/kernel/rules/severity.json +77 -0
- package/lib/client.js +4668 -3423
- package/lib/finding-guidance.js +6 -0
- package/lib/local-request.js +44 -0
- package/lib/report-contract.js +16 -1
- package/lib/upgrade-contract.js +1 -0
- package/package.json +10 -5
- package/scripts/prepublish-check.mjs +1 -22
- package/scripts/stage-contract.mjs +7 -0
- package/skill/SKILL.md +13 -12
|
@@ -4,6 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import { runWorkerTask } from "./worker-analysis.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
+
import { readStaticDirectory } from "./static-input.mjs";
|
|
7
8
|
|
|
8
9
|
export const sha256 = (bytes) =>
|
|
9
10
|
createHash("sha256").update(bytes).digest("hex");
|
|
@@ -52,7 +53,7 @@ export function captureUpgradeInputOwned(nm, overrides = {}, root) {
|
|
|
52
53
|
const before = fs.lstatSync(dir);
|
|
53
54
|
if (!before.isDirectory() || fs.realpathSync(dir) !== dir)
|
|
54
55
|
throw fail("目录身份变化");
|
|
55
|
-
const names =
|
|
56
|
+
const names = readStaticDirectory(dir, limits.entries - entries, check);
|
|
56
57
|
entries += names.length;
|
|
57
58
|
check();
|
|
58
59
|
records.push(["directory", dir, stamp(before), names]);
|
|
@@ -61,7 +62,7 @@ export function captureUpgradeInputOwned(nm, overrides = {}, root) {
|
|
|
61
62
|
const verifyDir = (dir, { names, before }) => {
|
|
62
63
|
if (
|
|
63
64
|
!same(stamp(before), stamp(fs.lstatSync(dir))) ||
|
|
64
|
-
!same(names,
|
|
65
|
+
!same(names, readStaticDirectory(dir, names.length, check)) ||
|
|
65
66
|
fs.realpathSync(dir) !== dir
|
|
66
67
|
)
|
|
67
68
|
throw fail("目录在读取时变化");
|
|
@@ -250,8 +251,12 @@ export function captureUpgradeInputOwned(nm, overrides = {}, root) {
|
|
|
250
251
|
if (first.signature !== second.signature)
|
|
251
252
|
throw fail("两次观察的目录、文件字节或链接身份不一致");
|
|
252
253
|
const freeze = (dir) => {
|
|
253
|
-
for (const
|
|
254
|
-
if (
|
|
254
|
+
for (const name of readStaticDirectory(dir, limits.entries, () => {
|
|
255
|
+
if (Date.now() - started > limits.timeMs) throw fail("捕获预算超限");
|
|
256
|
+
})) {
|
|
257
|
+
const child = path.join(dir, name);
|
|
258
|
+
if (fs.lstatSync(child).isDirectory()) freeze(child);
|
|
259
|
+
}
|
|
255
260
|
fs.chmodSync(dir, 0o500);
|
|
256
261
|
};
|
|
257
262
|
freeze(root);
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
/** Offline version prediction: no installation, runtime probe, cache or baseline. */
|
|
2
|
-
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
4
|
import { loadCatalog } from "./catalog.mjs";
|
|
6
5
|
import { TOOL_VERSION } from "./version.mjs";
|
|
7
|
-
import {
|
|
8
|
-
captureUpgradeInput,
|
|
9
|
-
sha256,
|
|
10
|
-
upgradeCleanupError,
|
|
11
|
-
} from "./upgrade-input.mjs";
|
|
6
|
+
import { captureUpgradeInput, upgradeCleanupError } from "./upgrade-input.mjs";
|
|
12
7
|
import { runWorkerTask } from "./worker-analysis.mjs";
|
|
13
|
-
import { assertReportBody } from "./contracts.mjs";
|
|
8
|
+
import { assertReportBody, terminalText } from "./contracts.mjs";
|
|
14
9
|
import {
|
|
15
10
|
SUPPORTED_PREVIEW_VERSIONS,
|
|
16
11
|
assertUpgradePreview,
|
|
@@ -114,13 +109,10 @@ export async function runUpgradePreview(input = {}, options = {}) {
|
|
|
114
109
|
};
|
|
115
110
|
const current = reviewedCatalogFor(currentVersion),
|
|
116
111
|
target = reviewedCatalogFor(targetVersion);
|
|
117
|
-
const rulesFile = path.join(ROOT, "rules", "severity.json"),
|
|
118
|
-
rulesBytes = fs.readFileSync(rulesFile);
|
|
119
|
-
const rules = JSON.parse(rulesBytes);
|
|
120
112
|
if (typeof input.nm !== "string" || !input.nm)
|
|
121
113
|
throw new Error("升级预检需要 node_modules 或 profile 路径");
|
|
122
114
|
snapshot = await captureUpgradeInput(input.nm, captureLimits, { signal });
|
|
123
|
-
const reports = await runWorkerTask(
|
|
115
|
+
const { rulesIdentity, ...reports } = await runWorkerTask(
|
|
124
116
|
new URL("./upgrade-worker.mjs", import.meta.url),
|
|
125
117
|
{
|
|
126
118
|
nm: snapshot.nm,
|
|
@@ -135,7 +127,7 @@ export async function runUpgradePreview(input = {}, options = {}) {
|
|
|
135
127
|
catalog: target.catalog,
|
|
136
128
|
evidence: target.evidence,
|
|
137
129
|
},
|
|
138
|
-
rules,
|
|
130
|
+
rulesFile: path.join(ROOT, "rules", "severity.json"),
|
|
139
131
|
},
|
|
140
132
|
{ timeoutMs, signal },
|
|
141
133
|
);
|
|
@@ -175,7 +167,7 @@ export async function runUpgradePreview(input = {}, options = {}) {
|
|
|
175
167
|
supportedVersions: [...SUPPORTED_PREVIEW_VERSIONS],
|
|
176
168
|
catalogs: { current: current.identity, target: target.identity },
|
|
177
169
|
artifact: snapshot.artifact,
|
|
178
|
-
rules:
|
|
170
|
+
rules: rulesIdentity,
|
|
179
171
|
...reports,
|
|
180
172
|
changes,
|
|
181
173
|
summary: {
|
|
@@ -213,7 +205,9 @@ export function renderUpgradePreview(result) {
|
|
|
213
205
|
...lines,
|
|
214
206
|
`无法判定:${result.error}`,
|
|
215
207
|
...result.limitations.map((x) => `限制:${x}`),
|
|
216
|
-
]
|
|
208
|
+
]
|
|
209
|
+
.map(terminalText)
|
|
210
|
+
.join("\n");
|
|
217
211
|
lines.push(
|
|
218
212
|
`新增 ${result.summary.added};变重 ${result.summary.worsened};消失 ${result.summary.resolved};减轻 ${result.summary.improved};不变 ${result.summary.unchanged}`,
|
|
219
213
|
);
|
|
@@ -252,5 +246,7 @@ export function renderUpgradePreview(result) {
|
|
|
252
246
|
for (const note of p.coverage.limitations)
|
|
253
247
|
lines.push(` 覆盖限制:${note}`);
|
|
254
248
|
}
|
|
255
|
-
return [...lines, ...result.limitations.map((x) => `限制:${x}`)]
|
|
249
|
+
return [...lines, ...result.limitations.map((x) => `限制:${x}`)]
|
|
250
|
+
.map(terminalText)
|
|
251
|
+
.join("\n");
|
|
256
252
|
}
|
|
@@ -4,8 +4,13 @@ import { buildVerdicts } from "./verdict.mjs";
|
|
|
4
4
|
import { sealReportBody } from "./contracts.mjs";
|
|
5
5
|
import { TOOL_VERSION } from "./version.mjs";
|
|
6
6
|
|
|
7
|
+
import { readStaticFile } from "./static-input.mjs";
|
|
8
|
+
import { sha256 } from "./upgrade-input.mjs";
|
|
9
|
+
|
|
7
10
|
try {
|
|
8
|
-
const { nm, current, target,
|
|
11
|
+
const { nm, current, target, rulesFile, artifact } = workerData;
|
|
12
|
+
const rulesBytes = readStaticFile(rulesFile, 8 * 1024 * 1024);
|
|
13
|
+
const rules = JSON.parse(rulesBytes);
|
|
9
14
|
const catalog = {
|
|
10
15
|
host: { ...current.catalog.host, ...target.catalog.host },
|
|
11
16
|
client: { ...current.catalog.client, ...target.catalog.client },
|
|
@@ -41,6 +46,10 @@ try {
|
|
|
41
46
|
parentPort.postMessage({
|
|
42
47
|
ok: true,
|
|
43
48
|
value: {
|
|
49
|
+
rulesIdentity: {
|
|
50
|
+
file: "rules/severity.json",
|
|
51
|
+
sha256: sha256(rulesBytes),
|
|
52
|
+
},
|
|
44
53
|
currentReport: reportFor(current),
|
|
45
54
|
targetReport: reportFor(target),
|
|
46
55
|
},
|
package/kernel/lib/verdict.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/** Pure evidence-driven compatibility verdicts. Unknown is not absence. */
|
|
2
2
|
import { normalizeObservation } from "./observation.mjs";
|
|
3
3
|
import { declaredSupport } from "./declarations.mjs";
|
|
4
|
+
import { checkApiSemantics } from "./api-semantics.mjs";
|
|
4
5
|
export const IMPACT_BASIS = { MEASURED: "measured", INFERRED: "inferred" };
|
|
5
6
|
export const CERTAINTY = { CERTAIN: "certain", SUSPECTED: "suspected" };
|
|
6
7
|
export const CATEGORY = {
|
|
8
|
+
API_ARGUMENT_INVALID: "api-argument-invalid",
|
|
9
|
+
API_OPTION_UNSUPPORTED: "api-option-unsupported",
|
|
7
10
|
HOST_SERVICE_MISSING: "host-service-missing",
|
|
8
11
|
HOST_METHOD_MISSING: "host-method-missing",
|
|
9
12
|
UNPROVIDED_DEPENDENCY: "unprovided-dependency",
|
|
@@ -190,7 +193,10 @@ export function buildVerdicts({
|
|
|
190
193
|
else if (row.state !== "ACTIVE")
|
|
191
194
|
limitations.push(`条目 ${row.id ?? name} 尚未稳定:${row.state}`);
|
|
192
195
|
}
|
|
193
|
-
if (p.clientHalf?.declared && p.clientHalf.ok === false)
|
|
196
|
+
if (p.clientHalf?.declared && p.clientHalf.ok === false) {
|
|
197
|
+
// Invalid declarations have no graph root to emit a source diagnostic.
|
|
198
|
+
if (p.clientHalf.declaredEntry == null)
|
|
199
|
+
limitations.push("客户端入口声明无法解析,无法确认客户端调用覆盖");
|
|
194
200
|
add({
|
|
195
201
|
category: CATEGORY.CLIENT_ENTRY_MISSING,
|
|
196
202
|
certainty: "certain",
|
|
@@ -204,6 +210,7 @@ export function buildVerdicts({
|
|
|
204
210
|
ruleSource: "static",
|
|
205
211
|
ruleRef: "plugins.client.half",
|
|
206
212
|
});
|
|
213
|
+
}
|
|
207
214
|
const self = new Set(p.providedServices || []);
|
|
208
215
|
for (const svc of p.services || []) {
|
|
209
216
|
const allSites = (p.callSites || []).filter((s) => s.service === svc);
|
|
@@ -263,6 +270,7 @@ export function buildVerdicts({
|
|
|
263
270
|
}
|
|
264
271
|
if (
|
|
265
272
|
(self.has(svc) || providers.has(svc)) &&
|
|
273
|
+
observed.status !== "absent" &&
|
|
266
274
|
!(observed.status === "present" && observed.complete)
|
|
267
275
|
) {
|
|
268
276
|
limitations.push(`${svc} 仅有提供声明,没有完整的运行时方法证据`);
|
|
@@ -432,6 +440,16 @@ export function buildVerdicts({
|
|
|
432
440
|
(s) => methods.includes(s.method) || protectedCall(s),
|
|
433
441
|
).length;
|
|
434
442
|
}
|
|
443
|
+
const semantic = checkApiSemantics(p, rules, dshVersion);
|
|
444
|
+
limitations.push(...semantic.limitations);
|
|
445
|
+
const semanticFindings = new Set(semantic.findings);
|
|
446
|
+
for (const finding of semantic.findings) {
|
|
447
|
+
add(finding);
|
|
448
|
+
account(finding.service, "finding", {
|
|
449
|
+
method: finding.method,
|
|
450
|
+
reason: "客户端静态参数契约风险;未实测业务影响",
|
|
451
|
+
});
|
|
452
|
+
}
|
|
435
453
|
const declaration = declarationOf(p, dshVersion, {
|
|
436
454
|
exemptions: compatibilityExemptions,
|
|
437
455
|
});
|
|
@@ -494,8 +512,13 @@ export function buildVerdicts({
|
|
|
494
512
|
: "package.peerDependencies",
|
|
495
513
|
});
|
|
496
514
|
}
|
|
497
|
-
|
|
498
|
-
|
|
515
|
+
const declarationConflicts = findings.filter(
|
|
516
|
+
(f) => !semanticFindings.has(f),
|
|
517
|
+
);
|
|
518
|
+
if (declaration?.claimsCompatible && declarationConflicts.length) {
|
|
519
|
+
const certain = declarationConflicts.every(
|
|
520
|
+
(f) => f.certainty === "certain",
|
|
521
|
+
);
|
|
499
522
|
add({
|
|
500
523
|
category: CATEGORY.DECLARATION_MISMATCH,
|
|
501
524
|
certainty: certain ? "certain" : "suspected",
|
|
@@ -504,13 +527,13 @@ export function buildVerdicts({
|
|
|
504
527
|
method: null,
|
|
505
528
|
location: null,
|
|
506
529
|
severity: "feature-dead",
|
|
507
|
-
impact: `声明兼容 DSH ${dshVersion},但发现 ${
|
|
530
|
+
impact: `声明兼容 DSH ${dshVersion},但发现 ${declarationConflicts.length} 项${certain ? "有证据的冲突" : "待核实风险"}`,
|
|
508
531
|
basis: certain
|
|
509
532
|
? "源码/运行时证据与兼容声明冲突"
|
|
510
533
|
: "原始发现含疑似证据,不能称为实测不符",
|
|
511
534
|
evidence: {
|
|
512
535
|
declared: declaration.raw,
|
|
513
|
-
measuredFindings:
|
|
536
|
+
measuredFindings: declarationConflicts.length,
|
|
514
537
|
},
|
|
515
538
|
ruleSource: "declaration",
|
|
516
539
|
ruleRef: null,
|
package/kernel/lib/version.mjs
CHANGED
|
@@ -1,37 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* ## 为什么必须有这个文件
|
|
5
|
-
*
|
|
6
|
-
* docs/Plan.md §1 约定的是「单仓库、**单版本**、一份 Node 内核、三个入口」。
|
|
7
|
-
* 而实测下来这条承诺**从来没兑现过**:
|
|
8
|
-
*
|
|
9
|
-
* | 产物 | 版本 |
|
|
10
|
-
* |---|---|
|
|
11
|
-
* | `plugin/package.json` | `0.0.1` |
|
|
12
|
-
* | CLI(`bin/compat-check.mjs`) | **没有** |
|
|
13
|
-
* | `dsh-plugin-compat-check.sh` | **没有** |
|
|
14
|
-
* | 报告 JSON | **没有** |
|
|
15
|
-
*
|
|
16
|
-
* 后果不是"不好看",而是**结论无法追溯**:用户拿着一条「`dsh-keep-awake` 部分可用」
|
|
17
|
-
* 来问"凭什么",我第一句必须问"你用的是哪一版工具" —— 而那个问题当时**无解**。
|
|
18
|
-
* 对同一个插件,规则表覆盖变了、提取器修了假阳性之后结论都会变。
|
|
19
|
-
*
|
|
20
|
-
* ## 为什么用 `.mjs` 而不是读 `package.json`
|
|
21
|
-
*
|
|
22
|
-
* 读者分四种形态存在:仓库检出、npm 装好的包(内核在 `kernel/`)、
|
|
23
|
-
* `--probe-boot` 起的临时 profile、以及 `.sh` 编排脚本。
|
|
24
|
-
* 它们的相对路径各不相同,**读 package.json 会在某一种形态下失败**。
|
|
25
|
-
* 一个常量没有这个问题,而且在打包时会被 `prepack` 一并带进包里。
|
|
26
|
-
*
|
|
27
|
-
* `plugin/package.json` 的 `version` 仍是 npm 需要的**字面量** ——
|
|
28
|
-
* 那一处由 `test/version.test.mjs` 钉住,对不上就报红并告诉你改哪里。
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
/** 语义化版本。改动它会同时影响三个入口的 `--version` 与报告 JSON 的 `toolVersion`。 */
|
|
32
|
-
export const TOOL_VERSION = '0.1.5';
|
|
33
|
-
|
|
34
|
-
/** 人类可读的一行,给 `--version` 用 */
|
|
1
|
+
/** Shared version source; package manifests are checked against this literal. */
|
|
2
|
+
export const TOOL_VERSION = '0.1.7';
|
|
35
3
|
export function versionLine() {
|
|
36
4
|
return `dsh-plugin-compat-check ${TOOL_VERSION}`;
|
|
37
5
|
}
|
|
@@ -1,3 +1,69 @@
|
|
|
1
|
+
// Shared by all ordinary analysis/identity jobs imported by this kernel instance.
|
|
2
|
+
const MAX_WORKERS = 2,
|
|
3
|
+
MAX_PENDING = 32;
|
|
4
|
+
let activeWorkers = 0;
|
|
5
|
+
const pendingWorkers = [];
|
|
6
|
+
const workerError = (code, message) =>
|
|
7
|
+
Object.assign(new Error(message), { code });
|
|
8
|
+
export function runReportWorkerTask(url, data, options = {}) {
|
|
9
|
+
const { signal, timeoutMs = 10000 } = options;
|
|
10
|
+
if (
|
|
11
|
+
signal &&
|
|
12
|
+
(typeof signal.aborted !== "boolean" ||
|
|
13
|
+
typeof signal.addEventListener !== "function" ||
|
|
14
|
+
typeof signal.removeEventListener !== "function")
|
|
15
|
+
)
|
|
16
|
+
return Promise.reject(new Error("Invalid worker signal"));
|
|
17
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647)
|
|
18
|
+
return Promise.reject(new Error("Invalid worker resource budget / 预算"));
|
|
19
|
+
if (signal?.aborted)
|
|
20
|
+
return Promise.reject(workerError("DSC_WORKER_ABORTED", "静态分析已取消"));
|
|
21
|
+
if (activeWorkers >= MAX_WORKERS && pendingWorkers.length >= MAX_PENDING)
|
|
22
|
+
return Promise.reject(
|
|
23
|
+
workerError("DSC_WORKER_CAPACITY", "工作线程排队容量已满,请稍后重试"),
|
|
24
|
+
);
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
let queued = false,
|
|
27
|
+
timer;
|
|
28
|
+
const cleanup = () => {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
signal?.removeEventListener("abort", cancel);
|
|
31
|
+
};
|
|
32
|
+
const remove = () => {
|
|
33
|
+
if (!queued) return false;
|
|
34
|
+
queued = false;
|
|
35
|
+
pendingWorkers.splice(pendingWorkers.indexOf(start), 1);
|
|
36
|
+
cleanup();
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
39
|
+
const cancel = () => {
|
|
40
|
+
if (remove())
|
|
41
|
+
reject(workerError("DSC_WORKER_ABORTED", "静态分析排队已取消"));
|
|
42
|
+
};
|
|
43
|
+
const start = () => {
|
|
44
|
+
queued = false;
|
|
45
|
+
cleanup();
|
|
46
|
+
activeWorkers++;
|
|
47
|
+
runWorkerTask(url, data, options)
|
|
48
|
+
.then(resolve, reject)
|
|
49
|
+
.finally(() => {
|
|
50
|
+
activeWorkers--;
|
|
51
|
+
pendingWorkers.shift()?.();
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
if (activeWorkers < MAX_WORKERS) start();
|
|
55
|
+
else {
|
|
56
|
+
queued = true;
|
|
57
|
+
pendingWorkers.push(start);
|
|
58
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
59
|
+
timer = setTimeout(() => {
|
|
60
|
+
if (remove())
|
|
61
|
+
reject(workerError("DSC_WORKER_TIMEOUT", "静态分析排队超过时间预算"));
|
|
62
|
+
}, timeoutMs);
|
|
63
|
+
if (signal?.aborted) cancel();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
1
67
|
import { Worker } from "node:worker_threads";
|
|
2
68
|
|
|
3
69
|
/** Supervise trusted jobs; settle only after reclaiming their worker thread. */
|
|
@@ -88,10 +154,15 @@ export function runWorkerTask(
|
|
|
88
154
|
});
|
|
89
155
|
}
|
|
90
156
|
|
|
91
|
-
export function extractInWorker(
|
|
92
|
-
|
|
157
|
+
export function extractInWorker(
|
|
158
|
+
nm,
|
|
159
|
+
options = {},
|
|
160
|
+
timeoutMs = 10000,
|
|
161
|
+
{ signal } = {},
|
|
162
|
+
) {
|
|
163
|
+
return runReportWorkerTask(
|
|
93
164
|
new URL("./extract-worker.mjs", import.meta.url),
|
|
94
165
|
{ nm, options },
|
|
95
|
-
{ timeoutMs },
|
|
166
|
+
{ timeoutMs, signal },
|
|
96
167
|
);
|
|
97
168
|
}
|
package/kernel/rules/README.md
CHANGED
|
@@ -1,226 +1,48 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 规则与固定源码参考
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
本文维护**0.1.7**的规则。契约见[Plan](../docs/Plan.md),维护见[流程](../docs/maintaining.md),限定证据/实际发布状态见[current-state](../docs/current-state.json)。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 文件与消费者
|
|
6
6
|
|
|
7
7
|
| 文件 | 用途 |
|
|
8
8
|
|---|---|
|
|
9
|
-
| severity.json |
|
|
10
|
-
| dsh-api-catalog.json
|
|
11
|
-
|
|
|
9
|
+
| severity.json | services下的方法严重度/影响与端侧约束;apiSemantics下的精确客户端参数、弃用和生命周期参考 |
|
|
10
|
+
| dsh-api-catalog.json | 固定0.2.0-rc.2目录 |
|
|
11
|
+
| dsh-api-catalog-0.2.1-rc.2.json / dsh-api-catalog-0.2.1-rc.3.json | 两个独立精确版本目录;不能因方法名相同而互相认证 |
|
|
12
|
+
| dsh-0.2.1-rc.3-review.json | 固定来源、摘要、签名与语义审查 |
|
|
13
|
+
| usage-inventory.json | 带日期、版本、profile和完整性标记的历史AST库存,不是当前覆盖率 |
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
普通检查对DSH≥0.2.1-rc.3选rc.3参考,≥0.2.1-rc.2选rc.2,其余/未知选旧目录,显式覆盖优先。登记字节与认证集合以`lib/contracts.mjs.CATALOG_REFERENCES`为准;升级预检接受三个登记精确版本,拒绝未知/范围。回退和自定义目录不认证未来宿主。
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
## 判定与解释
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
| 情形 | 处理 |
|
|
19
|
+
| 证据 | 处理 |
|
|
20
20
|
|---|---|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
27
|
-
方法规则中的degraded应有具体降级/回退依据;peer声明冲突的degraded是引擎针对声明风险的独立默认,不代表观察到回退。方法缺失但没有语义规则时会明确“影响面未知”。声明provide只是候选提供方,不证明已激活,不能遮住完整的present方法观测。
|
|
28
|
-
|
|
29
|
-
宿主、客户端及shared调用点必须分开。客户端方法不能拿宿主probe判缺失;同名服务也不保证同一语义。对源码归属不确定的Context/别名保留suspected。certainty与impactBasis分别表达证据强度和影响是否实测。
|
|
30
|
-
|
|
31
|
-
0.1.4补强:必需inject未观测保持degraded/suspected,仅提示激活前提未确认;同profile明确absent才推断激活障碍,optional/scoped依赖与方法缺失分别处理,不借业务答案调严重度。`--observe-services`另记服务预测身份/摘要,不覆盖静态FN,也不改变生产certainty或impactBasis。
|
|
32
|
-
|
|
33
|
-
仓库独立业务队列将手审的功能调用区间内API风险与真实市场操作、侧栏HTTP/编辑器及有限模型回复结果对照,保持业务、loader标签及预测隔离;不把整个插件的其它发现挪给本功能。正常/自然失败与HTTP503/控件缺失故障组分别计分,限定样本不能改变生产报告中的certainty或impactBasis,也不是新增严重度规则的充分证据。复现见[使用指南](../docs/guide.md#工程与真实集成)。
|
|
34
|
-
|
|
35
|
-
## 如何更新
|
|
36
|
-
|
|
37
|
-
ASAR读取器已修正数据起点为8+headerSize,并校验头长、边界、完整读取、UTF-8/JSON及归档SHA256。2026-10-18与Electron原生ASAR读取逐字节及首尾交叉校验后生成独立rc.2目录,旧目录未改。新host增加8项、移除invariants;subagents移除start/startContinuable/drainContinuableChildren/drainContinuableDescendants,增加startActivation/drainChildren/drainDescendants/waitForChildren;其它方法差异和源摘要保留在提交`10eaeba`的`docs/current-state.json`历史记录;当前认证字节见`lib/contracts.mjs`的`CATALOG_REFERENCES`,新候选按[维护流程](../docs/maintaining.md#api目录候选审查)核对。client方法集合未变。旧方法规则保留其原调用语义;不把新方法当同义重命名,也不只凭目录变化新增严重度,未知影响继续回退。
|
|
38
|
-
|
|
39
|
-
先读调用点和目标版本的真实描述符/官方源码,再修改规则,添加行为回归并审查快照差异。原needsReview已在2026-10-15逐条复核,决定与证据保留于severity.json的needsReview.reviewed/evidence及 [Plan](../docs/Plan.md)。
|
|
21
|
+
| 可靠映射且启用的FAILED/PENDING loader | fatal、影响measured;禁用/LOADING/无法映射不冒充同一事实 |
|
|
22
|
+
| 必需inject及合格同目标观测明确absent | 可推断激活障碍;仅目录缺项或未采样不证明缺失 |
|
|
23
|
+
| 非硬依赖明确absent或有充分方法缺失证据 | 按方法规则/默认档给feature-dead或degraded,影响通常inferred |
|
|
24
|
+
| 声明范围冲突或非法字段 | degraded/推断;有效精确豁免仅消除范围冲突,不放行非法字段或证明业务通过 |
|
|
25
|
+
| 未采样、访问器未知、不可读或源码不完整 | 保留覆盖限制,不用省略键认定不存在 |
|
|
26
|
+
| 固定客户端参数类型/枚举不符 | 非致命静态风险;未知值弃权,弃用/生命周期提醒仅作覆盖说明 |
|
|
40
27
|
|
|
41
|
-
|
|
28
|
+
方法自身layer优先、服务layer回退,客户端规则不能套宿主同名方法。提供方声明只是候选,不能掩盖明确absent;present服务上的方法缺失另判。certainty与impactBasis分别描述证据和影响,弱Context/别名保留suspected。没有语义依据的方法缺失明确“影响面未知”,不猜替代API或修复版本。
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
node tools/coverage.mjs --nm <node_modules目录> --dsh-version <已核验版本> --json
|
|
45
|
-
node tools/coverage.mjs --nm <node_modules目录> --probe <同目标的现代probe> --write --json
|
|
46
|
-
# 默认拒绝将不完整扫描写成库存;审查diagnostics后方可显式接受部分样本
|
|
47
|
-
node tools/coverage.mjs --nm <node_modules目录> --dsh-version <已核验版本> --write --allow-partial --json
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
仓库保存的库存是2026-10-15的desktop21插件样本,DSH0.2.0-rc.2,静态catalogue模式,不借用规则复核的私有web探针。扫描受默认字节预算和缺失CSS入口限制,明确为partial:50个有调用服务、125个分端方法对,其中88个有对应规则(host69/79、client19/46)。这些是本次读到的部分集合,不是完整覆盖率或准确率;同名方法不同端分别计数,不能直接和下面旧口径61%比较。sourceDigest记录规范化AST分析摘要,不承诺所有包字节的完整性。历史Python参考不是AST真值。
|
|
30
|
+
DSH≥0.2.1-rc.1优先engines.dsh,缺失回退官方peer,旧目标保留旧策略。独立compatibility.json仅按插件/DSH精确版本豁免;非法字段和无效豁免保留风险/限制。
|
|
51
31
|
|
|
52
|
-
|
|
32
|
+
summary/basis/evidence/knownGoneIn/needsReview是解释与维护资料。knownExtractionArtifacts只保留历史反例,不自动过滤当前真实调用。degraded方法规则须有降级/回退依据;声明与静态参数风险的degraded不冒充实测回退。
|
|
53
33
|
|
|
54
|
-
##
|
|
34
|
+
## 更新与度量
|
|
55
35
|
|
|
56
|
-
|
|
36
|
+
先核对调用点、端侧、目标版本及真实描述符/官方源码,再改规则并加行为回归;不能仅凭目录变化新增严重度。新版本目录、语义基线及来源验证按[维护流程](../docs/maintaining.md#api目录候选审查)分别审查,保留旧基线。
|
|
57
37
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
node tools/coverage.mjs --
|
|
64
|
-
node tools/coverage.mjs --probe <报告> --write # 顺带刷新 rules/usage-inventory.json
|
|
38
|
+
```sh
|
|
39
|
+
npm run check:catalogs
|
|
40
|
+
node tools/coverage.mjs --nm <目录> --dsh-version <精确版本> --json
|
|
41
|
+
node tools/coverage.mjs --nm <目录> --probe <同目标的现代probe> --write --json
|
|
42
|
+
# 仅审查diagnostics后才显式接受部分库存
|
|
43
|
+
node tools/coverage.mjs --nm <目录> --dsh-version <精确版本> --write --allow-partial --json
|
|
65
44
|
```
|
|
66
45
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- **"有调用点的服务"**:`perPlugin[*].calls` 的键的并集 —— **真的被调用过**的服务。
|
|
70
|
-
它**不是** `report.services`(后者含只被声明过的项,拿它当分母会把覆盖率算低)。
|
|
71
|
-
- **排除已知提取产物**,且**按方法粒度**:`knownExtractionArtifacts` 里逐条写了"不要为这些补规则"。
|
|
72
|
-
按服务粒度排会把 `sessions`/`jobs` 这些**真服务**一起排掉。
|
|
73
|
-
- **`--probe` 很要紧**:不给运行时面时,方法级结论一律降为「疑似」而**不进 findings**,
|
|
74
|
-
于是"落到默认档"这个数字既不是真实的缺口、也不是用户会看到的东西。
|
|
75
|
-
实测差别:同一个 profile,不给面 7、给面 **3**。
|
|
76
|
-
|
|
77
|
-
### 当时值(2026-10-11,desktop profile,DSH 0.2.0-rc.2,**带运行时面**)
|
|
78
|
-
|
|
79
|
-
| 口径 | 值 |
|
|
80
|
-
|---|---|
|
|
81
|
-
| 插件 | 21 |
|
|
82
|
-
| 有调用点的服务 | **49**(原始 50,排掉 1 个纯产物服务) |
|
|
83
|
-
| 表中覆盖的服务 | **36 / 49 = 73%** |
|
|
84
|
-
| (服务,方法) 对 | **161**(排掉 4 个产物对) |
|
|
85
|
-
| 表中有方法级规则的对 | **99 / 161 = 61%** |
|
|
86
|
-
| 表中服务总数 / 其中有方法级子表 | 42 / 37 |
|
|
87
|
-
| **引擎实测:发现总数 / 规则表命中 / 落到默认档** | **4 / 1 / 3** |
|
|
88
|
-
|
|
89
|
-
**最后一行才是唯一真正有意义的口径** —— 前几行都只是它的分解。
|
|
90
|
-
|
|
91
|
-
### 「落到默认档」的那 3 条**不该补规则**
|
|
92
|
-
|
|
93
|
-
它们全是 `unprovided-dependency`(**插件间依赖**,`method` 为 null),而且
|
|
94
|
-
`evidence.inHostCatalog === false` —— 也就是说**它们根本不是 DSH API**。
|
|
95
|
-
类别级判定已经给出了准确的影响面("依赖的服务由其它插件提供,当前 profile 里没有提供方")。
|
|
96
|
-
给它们补"服务级规则"等于把"插件之间的依赖关系"伪装成"与 DSH 版本不兼容"。
|
|
97
|
-
|
|
98
|
-
### 「未覆盖的 (服务,方法) 对」里剩下的都是什么
|
|
99
|
-
|
|
100
|
-
按服务是否在**官方宿主目录**里分开看(这是"归不归规则表管"的分界):
|
|
101
|
-
|
|
102
|
-
| 类别 | 条数 | 该不该补规则 |
|
|
103
|
-
|---|---|---|
|
|
104
|
-
| 服务在官方宿主目录里 | **1**(`sessions.has`) | **不该** —— 实测它是 `ws.sessions`(别人的 Map 字段)造的产物,已进 `knownExtractionArtifacts` |
|
|
105
|
-
| 服务**不在**目录里 | 74 | **不该** —— 它们是**插件自有服务**(`betterSidebar`/`betterLocale`/`configForms`/`sidebarRightTabs`…)或已知产物(`parser.*`/`ruleNames.*`/集合方法名) |
|
|
106
|
-
|
|
107
|
-
**所以规则表在"DSH API"这个范围内是完整的**:凡是真实存在、且被真实调用的宿主 API,
|
|
108
|
-
要么有规则,要么已确证是提取产物。**剩下没规则的,都不是 DSH 的 API。**
|
|
109
|
-
|
|
110
|
-
## 历史补充:新增条目的证据分列
|
|
111
|
-
|
|
112
|
-
### 一、有官方 API 目录直接依据(27 条)
|
|
113
|
-
|
|
114
|
-
**方法在目录里声明,且描述写明了它干什么(`basis: catalog`,24 条)**
|
|
115
|
-
|
|
116
|
-
| 服务 | 方法 |
|
|
117
|
-
|---|---|
|
|
118
|
-
| `skills` | `registerProvider` |
|
|
119
|
-
| `subagents` | `list`、`getProvider`、`startContinuable`、`interrupt`、`listDescendants`、`drainContinuableChildren` |
|
|
120
|
-
| `sessionQuery` | `listSessions`、`readSession`、`observeSession` |
|
|
121
|
-
| `sessionTitle` | `rename` |
|
|
122
|
-
| `typert` | `register` |
|
|
123
|
-
| `agentPresets` | `resolve`、`mount` |
|
|
124
|
-
| `agentTeams` | `tryMembership` |
|
|
125
|
-
| `sessions`(客户端侧) | `scope`、`search` |
|
|
126
|
-
| `locale` | `getLocale`、`addLanguage` |
|
|
127
|
-
| `theme` | `register` |
|
|
128
|
-
| `layout` | `selectPanel` |
|
|
129
|
-
| `uiWorkspace` | `openSession` |
|
|
130
|
-
| `workspaces` | `create`、`delete` |
|
|
131
|
-
|
|
132
|
-
**目录不声明方法,但官方 description 写明了该方法(`basis: catalog-description`,3 条)**
|
|
133
|
-
|
|
134
|
-
| 服务 | 方法 | 依据原文(节选) |
|
|
135
|
-
|---|---|---|
|
|
136
|
-
| `credentials` | `resolve` | "an empty stored value is absent everywhere — `resolve` skips it" |
|
|
137
|
-
| `sessionPersistence` | `open`、`list` | "a created session is observable through `stat`/`list`/`open`" |
|
|
138
|
-
|
|
139
|
-
### 二、从真实调用点推断(`basis: callsite`,7 条)
|
|
140
|
-
|
|
141
|
-
这 7 条都给了 `文件:行`,请按 `evidence` 去读一眼再采信。
|
|
142
|
-
|
|
143
|
-
| 服务.方法 | 依据 |
|
|
144
|
-
|---|---|
|
|
145
|
-
| `llm.listProviders` | `@liustack/modlens/dsh/index.js:397`(`typeof` 探测不成立就整体跳过提供方名解析 → 退回显示上游 id) |
|
|
146
|
-
| `settings.installSection` | `@nonamelego/dsh-catppuccin/lib/index.js:734`,且其注释写明该通道对应 DSH ≤ 0.1.6-alpha.2,0.1.7-alpha.1 起移除 |
|
|
147
|
-
| `storageDomain.open` | `@michengai/dsh-archive-manager/lib/projcache.js:102`。**注意**:目录声明的是 `closeAll`/`get`,没有 `open`,但 description 写的是 "Opens declared domains over routed backends"。方法名与目录对不上,所以只给 `callsite` |
|
|
148
|
-
| `sessions.liveEntryFor` | `@michengai/dsh-archive-manager/lib/workspace.js:932` |
|
|
149
|
-
| `sessions.detachEntered` | 同上一行 `:933`;与官方 `enter()` description 里"返回 detach disposer"那一半互为印证 |
|
|
150
|
-
| `sessions.using` | `@michengai/dsh-archive-manager/lib/client.js`(`using` 不可用即退回 `binding`) |
|
|
151
|
-
| `timer.timeout` | `dsh-recall-plugin/lib/client.js:1430`(不可用即用原生 `setTimeout`) |
|
|
152
|
-
|
|
153
|
-
## 历史补充:故意没填的服务与原因
|
|
154
|
-
|
|
155
|
-
未进入本表的 18 个实际被用到的服务。**这一节和填了的那些一样重要**——乱填一个影响面,等于毁掉本工具唯一的资产。
|
|
156
|
-
|
|
157
|
-
### A. 第三方插件自己的服务,不是 DSH API(8 个)
|
|
158
|
-
|
|
159
|
-
`betterLocale`、`companionQq`、`pet`、`remote`、`sidebarRight`、`sidebarRightTabs`、`pluginNavigation`、`webUiSettings`
|
|
160
|
-
|
|
161
|
-
判定依据是**取得方式**:它们全部由客户端 `ctx.get("x")` / `ctx.inject(["x"], …)` 软取,而提供方是另一个插件:
|
|
162
|
-
|
|
163
|
-
- `companionQq` —— dsh-meme 的源码注释直接写明"消费 dsh-companion 提供的 `companionQq` 服务(sendImage/isOnline)"。
|
|
164
|
-
- `webUiSettings` —— remote-web-ui 的注释写明"由 dsh-web-settings 这个包在 `apply()` 期间发布"。
|
|
165
|
-
- `pluginNavigation` —— dsh-mnemon 在自己的客户端 `inner.effect(() => seats.configuration.provide(...))` 里挂上去。
|
|
166
|
-
- `sidebarRight` / `sidebarRightTabs` —— dsh-better-sidebar 客户端软取/inject 的同族服务。
|
|
167
|
-
|
|
168
|
-
对这类服务,正确的结论就是引擎现在给的那句默认话术:「该服务由另一个插件提供,当前 profile 里没有提供方」。**写进本表反而会说谎**——本表的语义是"DSH 的某个 API 没了",而这些名字根本不在 DSH 的 API 面上。
|
|
169
|
-
|
|
170
|
-
### B. 由社区启动器提供,官方 shell 上没有(1 个)
|
|
171
|
-
|
|
172
|
-
`desktopProfiles` —— @nonamelego/dsh-catppuccin 的类型注释写得很清楚:"`desktopProfiles.current.name` when the community launcher exposes that service; **undefined on the official shell**"。它不是官方 API。
|
|
173
|
-
|
|
174
|
-
### C. 版本相关的旧/新客户端 seam,0.2.0-rc.2 都不在目录里(2 个)
|
|
175
|
-
|
|
176
|
-
`settingsScope`、`configForms` —— catppuccin 的注释把版本线说得很明白:`settingsScope` 属于 DSH ≤ 0.1.6-alpha.2 的旧 seam,`configForms` 是 ≥ 0.1.7-alpha.1 的新 seam,而 0.2.0-rc.2 的客户端目录里两个都没有。缺它属于"版本演进",不属于"插件用了不存在的 API",而且插件都做了 `typeof` 探测回退。**记录在案,不填。**
|
|
177
|
-
|
|
178
|
-
### D. 提取误报,不是 API(3 个)
|
|
179
|
-
|
|
180
|
-
`parser`(40 个"方法")、`ruleNames`、`topoIndex` —— 来自 `dsh-better-sidebar/lib/client-mermaid.js` 里 vendored 的 markdown 解析器上下文。已在 `severity.json` 的 `knownExtractionArtifacts` 里逐条登记。
|
|
181
|
-
|
|
182
|
-
### E. 判不准,所以不填(3 个)
|
|
183
|
-
|
|
184
|
-
| 服务 | 为什么判不准 |
|
|
185
|
-
|---|---|
|
|
186
|
-
| `conversation` | 只出现在客户端代码(dsh-meme / better-sidebar / git-graph / recall)里,宿主与客户端两个官方目录都没有。既可能是尚未公开的客户端服务,也可能是误报——分不出来 |
|
|
187
|
-
| `webStartup` | remote-web-ui 软取它读 `host`/`port`;两个目录都没有。可能是启动器/宿主不公开的面,无法判断缺失意味着什么 |
|
|
188
|
-
| `styles` | dsh-recall-plugin 用 `ctx.get("styles")` 并**自带 `document` 注入兜底**(回退路径明确),但同名标识符也出现在 `dsh-better-sidebar/lib/client-mermaid.js` 的 vendored 代码里——无法确认是哪一边 |
|
|
189
|
-
|
|
190
|
-
### F. 提取器已正确处理,无需规则(1 个)
|
|
191
|
-
|
|
192
|
-
`betterSidebar` —— 它是插件自己 `ctx.provide()` 出来的服务,**提取器已正确记入 `providedServices`**(同类的还有 dshmarket 的 `market`),因此不会产生假缺口。`pet` / `agentTeams` / `companionQq` / `betterLocale` 这些**没有**被 `ctx.provide()` 捕获的(见 `docs/Plan.md` §2.2 的提供方证据约束),就落进了上面 A 组。
|
|
193
|
-
|
|
194
|
-
## 历史补充:已知提取误报
|
|
195
|
-
|
|
196
|
-
`severity.json` 里有一个 `knownExtractionArtifacts` 段,逐条登记了"看起来是 API 缺口、其实是正则产物"的 `服务.方法`,并给出证据。**这一节存在的意义是防止后来者(包括未来的我)出于好心把假阳性升格成"有影响面的确定结论"。**
|
|
197
|
-
|
|
198
|
-
最值得记住的两个(当时新查出来的,不只是继承 `usage-inventory.json` 的已知缺陷):
|
|
199
|
-
|
|
200
|
-
1. **alias 正则漏了词边界**:`lib/extract.mjs` 里 `ctx.get("x")` 的别名规则构造的正则是 `别名\.方法(`,**没有 `\b`**。于是源码里的 `subagents.listDescendants(` 会同时命中 `agents.listDescendants`——因为 `subagents` 里含子串 `agents.`。desktop profile 上正好 2 处。改前它一直以"影响面未知"的形式出现在结论里。
|
|
201
|
-
2. **同文件内别名撞名**:dsh-better-sidebar 的 `lib/client.js` 里同时有 `const service = ctx.get("jobs")`(1 处)和 `const service = ctx.get("betterSidebar")`(6 处)。别名规则对文件内**每一次**匹配都按每个别名目标各记一次,于是文件里所有 `service.X()`——**包括 JSDoc 注释里的**——都被算到 `jobs` 头上。`jobs.getTabs`/`closeTab`/`registerTab` 等 13 个"方法"就是这么来的(`lib/client.js:977` 那处原文是注释里的一句 `service.getTabs()`)。
|
|
202
|
-
|
|
203
|
-
其余登记项:`settings.isTrusted`/`reportNonstrict`/`useStrictBehavior`(KaTeX 的 `parser.settings`)、`settings.settingsGet`/`externalDisable`(better-sidebar 自有的 `SidebarSettingsFace`)、`locale.startsWith`(字符串方法)、`tools.filter/join/push` 与 `sessions.map/filter/slice/...`(数组方法)。
|
|
204
|
-
|
|
205
|
-
## 历史待复核登记(2026-10-11;现已按上文处理)
|
|
206
|
-
|
|
207
|
-
改这些条目会改变输出,按当时的维护约定应先摊开差异再决定,故仅登记证据。以下保留当时的疑问;当前处理决定在needsReview.reviewed中:
|
|
208
|
-
|
|
209
|
-
- `sessions.open` / `sessions.getSnapshot`:两个目录里都没有;客户端真正的跳转入口是 `uiWorkspace.openSession`。
|
|
210
|
-
- `sessions.refresh`:两个目录里都没有(客户端目录里的是 `refreshProjections`)。
|
|
211
|
-
- `settings.get`:0.2.0-rc.2 的宿主目录里 `settings` 没有 `get`,可能和 `settingsGet` 一样来自插件自有的设置面。
|
|
212
|
-
- `attachments.readImage`:目录里是 `readImageRequest`,方法名对不上,需要一次运行时探针定音。
|
|
213
|
-
|
|
214
|
-
## 当前提取边界
|
|
215
|
-
|
|
216
|
-
旧inventory来源于正则提取器;当前JSON已由有版本和完整性标记的AST样本替换,旧数值只保留在本页历史表。knownExtractionArtifacts也保留历史反例用途,并非当前引擎的黑名单。新的规则刷新必须记录DSH版本、目标profile、probe身份与时间,并逐条复核源码归属。
|
|
217
|
-
|
|
218
|
-
真解析器已经落地;“疑似比例明显下降”不作为验收指标,因为该比例还受运行时采样覆盖影响。当前已支持有界跨函数/包内模块值流,未知动态行为仍有诊断;不声称完整分析,边界见 [现行契约](../docs/Plan.md)。
|
|
219
|
-
|
|
220
|
-
## 当前维护原则
|
|
221
|
-
|
|
222
|
-
直接改 `severity.json` 并说明依据。特别注意三类容易判错的:
|
|
46
|
+
coverage按AST调用点分host/client/shared统计规则命中及默认影响;不借异profile探针。不完整扫描默认拒绝写库存,写入须合法精确DSH版本,坏版本不覆盖旧文件。文件树/AST摘要不是原子快照或包签名。
|
|
223
47
|
|
|
224
|
-
-
|
|
225
|
-
- **降级 ≠ 无害**:`logger`相关规则按日志/诊断影响描述,不外推全部业务功能均不受影响;但 `jobs.onJobsChanged` 判 `degraded` 是因为它**有明确的轮询回退**,而不是因为它不重要。
|
|
226
|
-
- **判不准就不要填**:落到默认档 + 明确的"影响面未知",比编一个看起来合理的影响面好得多。新疑问写入needsReview并保留来源和待核验范围,不把历史“故意没填”当永久结论。
|
|
48
|
+
库存是2026-10-15、DSH0.2.0-rc.2的desktop历史部分样本;规则复核、旧覆盖率和方法决定可在`39d9f1a:rules/README.md`及severity.json的reviewed/evidence追溯。它们不代表当前生态准确率。真实业务标签、静态/service-only预测和驱动错误独立计分,不借业务答案调严重度;[复现](../docs/maintaining.md#真实插件回归)。
|