@openclaw/plugin-inspector 0.3.19 → 0.3.20
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/CHANGELOG.md +13 -0
- package/README.md +11 -3
- package/package.json +7 -2
- package/src/advanced.js +7 -0
- package/src/api.js +4 -0
- package/src/batch.js +8 -0
- package/src/cli.js +33 -10
- package/src/compatibility-report.js +6 -0
- package/src/config.js +2 -1
- package/src/fixture-summary.js +162 -20
- package/src/index.js +12 -3
- package/src/inspector.js +170 -11
- package/src/issues.js +4 -1
- package/src/openclaw-target.js +185 -2
- package/src/openclaw-version.js +246 -0
- package/src/report.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.20 - 2026-07-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Resolve `latest`, `beta`, or exact OpenClaw npm versions for compatibility inspection, reuse prepared targets from cache, and report exact target/source metadata.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Treat version-derived API removals inside a plugin's declared OpenClaw range as author-facing errors while keeping out-of-range targets informational, including beta eligibility against the upcoming stable version.
|
|
12
|
+
- Accept tested minimum OpenClaw host versions below a plugin's build version without reporting package metadata drift.
|
|
13
|
+
- Ignore type-only OpenClaw SDK imports when classifying runtime compatibility against a target release.
|
|
14
|
+
- Accept `openclaw.runtimeSetupEntry` and other paired compiled runtime entrypoints as valid replacements for absent source entrypoints.
|
|
15
|
+
|
|
3
16
|
## 0.3.19 - 2026-07-27
|
|
4
17
|
|
|
5
18
|
### Highlights
|
package/README.md
CHANGED
|
@@ -34,6 +34,12 @@ checkout. If an OpenClaw checkout is supplied with `--openclaw <path>`, the
|
|
|
34
34
|
inspector only reads public compatibility surfaces such as compat records, SDK
|
|
35
35
|
exports, hook names, manifest fields, and registrar metadata.
|
|
36
36
|
|
|
37
|
+
Pass `--openclaw-version latest`, `--openclaw-version beta`, or an exact
|
|
38
|
+
published version to resolve an official OpenClaw npm target. Reports keep the
|
|
39
|
+
exact resolved version and npm source metadata. Prepared targets are cached, so
|
|
40
|
+
batch and repeated inspections do not download the same OpenClaw package for
|
|
41
|
+
each plugin.
|
|
42
|
+
|
|
37
43
|
## Quick Start
|
|
38
44
|
|
|
39
45
|
Run this from a plugin package root:
|
|
@@ -181,6 +187,7 @@ Common options:
|
|
|
181
187
|
| `--config <path>` | Read a standalone config file. Required for fixture-suite `report`. |
|
|
182
188
|
| `--out <dir>` | Write reports somewhere other than `reports/`. |
|
|
183
189
|
| `--openclaw <path>` | Compare against a local OpenClaw checkout. |
|
|
190
|
+
| `--openclaw-version latest\|beta\|<exact>` | Resolve and compare against an official OpenClaw npm release. |
|
|
184
191
|
| `--no-openclaw` | Disable OpenClaw checkout comparison. |
|
|
185
192
|
| `--runtime` / `--capture` | Add opt-in runtime registration capture. |
|
|
186
193
|
| `--no-runtime` / `--no-capture` | Disable runtime capture even when config enables it. |
|
|
@@ -298,7 +305,7 @@ Important report sections:
|
|
|
298
305
|
| --- | --- |
|
|
299
306
|
| `status` | `pass` unless hard breakages exist. |
|
|
300
307
|
| `summary` | Counts for fixtures, breakages, warnings, suggestions, issues, issue classes, and contract probes. |
|
|
301
|
-
| `targetOpenClaw` |
|
|
308
|
+
| `targetOpenClaw` | Exact resolved version, source metadata, cache state, and public compatibility data for the selected OpenClaw target. |
|
|
302
309
|
| `fixtures` | Per-plugin metadata, hooks, registrations, manifest contracts, package data, SDK imports, and SDK deprecation evidence. |
|
|
303
310
|
| `breakages` | Blocking compatibility failures. |
|
|
304
311
|
| `warnings` / `suggestions` | Non-blocking compatibility findings. |
|
|
@@ -309,8 +316,9 @@ Important report sections:
|
|
|
309
316
|
|
|
310
317
|
Default `check`, `ci`, and `batch` reports include both author-facing and
|
|
311
318
|
internal findings. Pass `--author-facing` when producing plugin-author output;
|
|
312
|
-
that filtered view includes only findings with `authorRemediation.summary
|
|
313
|
-
`authorRemediation.docsUrl
|
|
319
|
+
that filtered view includes only findings with `authorRemediation.summary`.
|
|
320
|
+
Curated fixes include `authorRemediation.docsUrl`; dynamically discovered API
|
|
321
|
+
removals use generic remediation without inventing a documentation URL.
|
|
314
322
|
Current author-facing deprecation warnings include deprecated SDK helpers such
|
|
315
323
|
as `loadSessionStore(...)` when a plugin still depends on the legacy whole-store
|
|
316
324
|
session shape.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/plugin-inspector",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.20",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Offline compatibility inspector for OpenClaw plugins.",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"./execution-results": "./src/execution-results.js",
|
|
32
32
|
"./import-loop-profile": "./src/import-loop-profile.js",
|
|
33
33
|
"./openclaw-target": "./src/openclaw-target.js",
|
|
34
|
+
"./openclaw-version": "./src/openclaw-version.js",
|
|
34
35
|
"./platform-probes": "./src/platform-probes.js",
|
|
35
36
|
"./profile-diff": "./src/profile-diff.js",
|
|
36
37
|
"./ref-diff": "./src/ref-diff.js",
|
|
@@ -69,5 +70,9 @@
|
|
|
69
70
|
"plugin",
|
|
70
71
|
"compatibility",
|
|
71
72
|
"ci"
|
|
72
|
-
]
|
|
73
|
+
],
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"semver": "^7.8.5",
|
|
76
|
+
"tar": "^7.5.22"
|
|
77
|
+
}
|
|
73
78
|
}
|
package/src/advanced.js
CHANGED
|
@@ -116,6 +116,13 @@ export {
|
|
|
116
116
|
parseTypeFields,
|
|
117
117
|
readOpenClawTargetSurface,
|
|
118
118
|
} from "./openclaw-target.js";
|
|
119
|
+
export {
|
|
120
|
+
openClawEligibilityVersion,
|
|
121
|
+
prepareOpenClawTarget,
|
|
122
|
+
resolveOpenClawTargetVersion,
|
|
123
|
+
satisfiesOpenClawCompatibilityRange,
|
|
124
|
+
satisfiesOpenClawVersionRange,
|
|
125
|
+
} from "./openclaw-version.js";
|
|
119
126
|
export {
|
|
120
127
|
captureEntrypoint,
|
|
121
128
|
captureEntrypointWithMockSdk,
|
package/src/api.js
CHANGED
|
@@ -46,6 +46,7 @@ export async function inspectPluginRoot(options = {}) {
|
|
|
46
46
|
authorFacing: options.authorFacing,
|
|
47
47
|
generatedAt: options.generatedAt,
|
|
48
48
|
openclawPath: options.openclawPath,
|
|
49
|
+
openclawVersion: options.openclawVersion,
|
|
49
50
|
executionResults: options.executionResults,
|
|
50
51
|
targetOpenClaw: options.targetOpenClaw,
|
|
51
52
|
});
|
|
@@ -62,6 +63,7 @@ export async function inspectCompatibilityFixtureSetConfig(options = {}) {
|
|
|
62
63
|
authorFacing: options.authorFacing,
|
|
63
64
|
generatedAt: options.generatedAt,
|
|
64
65
|
openclawPath: options.openclawPath,
|
|
66
|
+
openclawVersion: options.openclawVersion,
|
|
65
67
|
executionResults: options.executionResults,
|
|
66
68
|
targetOpenClaw: options.targetOpenClaw,
|
|
67
69
|
});
|
|
@@ -117,6 +119,7 @@ export async function buildFixtureSetColdImportReadiness(options = {}) {
|
|
|
117
119
|
authorFacing: options.authorFacing,
|
|
118
120
|
generatedAt: options.generatedAt,
|
|
119
121
|
openclawPath: options.openclawPath,
|
|
122
|
+
openclawVersion: options.openclawVersion,
|
|
120
123
|
executionResults: options.executionResults,
|
|
121
124
|
targetOpenClaw: options.targetOpenClaw,
|
|
122
125
|
}));
|
|
@@ -150,6 +153,7 @@ export async function buildFixtureSetWorkspacePlan(options = {}) {
|
|
|
150
153
|
authorFacing: options.authorFacing,
|
|
151
154
|
generatedAt: options.generatedAt,
|
|
152
155
|
openclawPath: options.openclawPath,
|
|
156
|
+
openclawVersion: options.openclawVersion,
|
|
153
157
|
executionResults: options.executionResults,
|
|
154
158
|
targetOpenClaw: options.targetOpenClaw,
|
|
155
159
|
}));
|
package/src/batch.js
CHANGED
|
@@ -4,6 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { renderMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
|
|
6
6
|
import { runPluginCheck } from "./api.js";
|
|
7
|
+
import { prepareOpenClawTarget, resolveOpenClawTargetVersion } from "./openclaw-version.js";
|
|
7
8
|
|
|
8
9
|
const ignoredDirs = new Set([
|
|
9
10
|
".git",
|
|
@@ -22,6 +23,11 @@ export async function runBatchAnalysis(options = {}) {
|
|
|
22
23
|
const outRoot = path.resolve(rootDir, outDir);
|
|
23
24
|
const concurrency = Math.max(1, Math.min(Math.round(options.concurrency ?? 4), 32));
|
|
24
25
|
const keepPluginReports = options.keepPluginReports === true;
|
|
26
|
+
const targetOpenClaw =
|
|
27
|
+
options.targetOpenClaw ??
|
|
28
|
+
(options.openclawVersion
|
|
29
|
+
? await prepareOpenClawTarget(await resolveOpenClawTargetVersion(options.openclawVersion, options), options)
|
|
30
|
+
: undefined);
|
|
25
31
|
const pluginRoots = await discoverPluginRoots(rootDir);
|
|
26
32
|
const tempRoot = keepPluginReports ? null : await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-batch-"));
|
|
27
33
|
const entries = [];
|
|
@@ -37,6 +43,7 @@ export async function runBatchAnalysis(options = {}) {
|
|
|
37
43
|
rootDir,
|
|
38
44
|
outDir: reportsRoot,
|
|
39
45
|
openclawPath: options.openclawPath,
|
|
46
|
+
targetOpenClaw,
|
|
40
47
|
}),
|
|
41
48
|
);
|
|
42
49
|
});
|
|
@@ -113,6 +120,7 @@ async function inspectBatchPlugin(pluginRoot, options) {
|
|
|
113
120
|
configPath: options.configPath,
|
|
114
121
|
mockSdk: options.mockSdk,
|
|
115
122
|
openclawPath: options.openclawPath,
|
|
123
|
+
targetOpenClaw: options.targetOpenClaw,
|
|
116
124
|
outDir: options.outDir,
|
|
117
125
|
pluginRoot,
|
|
118
126
|
});
|
package/src/cli.js
CHANGED
|
@@ -57,9 +57,10 @@ try {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
async function runBatch(commandArgs) {
|
|
60
|
-
const inputDir = readFirstPositional(commandArgs, new Set(["--out", "--openclaw", "--concurrency"]));
|
|
60
|
+
const inputDir = readFirstPositional(commandArgs, new Set(["--out", "--openclaw", "--openclaw-version", "--concurrency"]));
|
|
61
61
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
62
62
|
const openclawPath = commandArgs.includes("--no-openclaw") ? false : readFlag(commandArgs, "--openclaw");
|
|
63
|
+
const openclawVersion = readOpenClawVersion(commandArgs);
|
|
63
64
|
const concurrency = Number(readFlag(commandArgs, "--concurrency") ?? "4");
|
|
64
65
|
const json = commandArgs.includes("--json");
|
|
65
66
|
const check = commandArgs.includes("--check");
|
|
@@ -72,6 +73,7 @@ async function runBatch(commandArgs) {
|
|
|
72
73
|
rootDir: inputDir,
|
|
73
74
|
outDir,
|
|
74
75
|
openclawPath,
|
|
76
|
+
openclawVersion,
|
|
75
77
|
concurrency,
|
|
76
78
|
authorFacing,
|
|
77
79
|
keepPluginReports,
|
|
@@ -105,6 +107,7 @@ async function runCheck(commandArgs, options = {}) {
|
|
|
105
107
|
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
106
108
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
107
109
|
const openclawPath = commandArgs.includes("--no-openclaw") ? false : readFlag(commandArgs, "--openclaw");
|
|
110
|
+
const openclawVersion = readOpenClawVersion(commandArgs);
|
|
108
111
|
const json = commandArgs.includes("--json");
|
|
109
112
|
const capture = readRuntimeFlag(commandArgs);
|
|
110
113
|
const mockSdk = readMockSdkFlag(commandArgs);
|
|
@@ -118,6 +121,7 @@ async function runCheck(commandArgs, options = {}) {
|
|
|
118
121
|
configPath,
|
|
119
122
|
mockSdk,
|
|
120
123
|
openclawPath,
|
|
124
|
+
openclawVersion,
|
|
121
125
|
outDir,
|
|
122
126
|
pluginRoot,
|
|
123
127
|
});
|
|
@@ -169,15 +173,17 @@ async function runReport(command, commandArgs) {
|
|
|
169
173
|
const configPath = readFlag(commandArgs, "--config");
|
|
170
174
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
171
175
|
const openclawPath = commandArgs.includes("--no-openclaw") ? false : readFlag(commandArgs, "--openclaw");
|
|
176
|
+
const openclawVersion = readOpenClawVersion(commandArgs);
|
|
172
177
|
const check = commandArgs.includes("--check") || command === "ci";
|
|
173
178
|
const json = commandArgs.includes("--json");
|
|
174
179
|
const ciOutputs = readCiOutputFlags(commandArgs);
|
|
175
180
|
const authorFacing = readAuthorFacingFlag(commandArgs);
|
|
176
181
|
const config = await loadInspectorConfig(configPath);
|
|
177
|
-
const
|
|
178
|
-
|
|
182
|
+
const compatibilityInspection = authorFacing || typeof openclawPath === "string" || openclawVersion !== null;
|
|
183
|
+
const report = compatibilityInspection
|
|
184
|
+
? await inspectCompatibilityFixtureSet(config, { authorFacing, openclawPath, openclawVersion })
|
|
179
185
|
: await inspectFixtureSet(config);
|
|
180
|
-
const paths =
|
|
186
|
+
const paths = compatibilityInspection
|
|
181
187
|
? await writeCompatibilityReport(report, { cwd: config.rootDir, outDir })
|
|
182
188
|
: await writeReport(report, { outDir });
|
|
183
189
|
await writeCiOutputArtifacts(report, {
|
|
@@ -202,6 +208,7 @@ async function runCi(commandArgs) {
|
|
|
202
208
|
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
203
209
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
204
210
|
const openclawPath = commandArgs.includes("--no-openclaw") ? false : readFlag(commandArgs, "--openclaw");
|
|
211
|
+
const openclawVersion = readOpenClawVersion(commandArgs);
|
|
205
212
|
const json = commandArgs.includes("--json");
|
|
206
213
|
const capture = readRuntimeFlag(commandArgs);
|
|
207
214
|
const mockSdk = readMockSdkFlag(commandArgs);
|
|
@@ -215,6 +222,7 @@ async function runCi(commandArgs) {
|
|
|
215
222
|
configPath,
|
|
216
223
|
mockSdk,
|
|
217
224
|
openclawPath,
|
|
225
|
+
openclawVersion,
|
|
218
226
|
outDir,
|
|
219
227
|
pluginRoot,
|
|
220
228
|
});
|
|
@@ -256,12 +264,13 @@ async function runCiCompatibilityReport({
|
|
|
256
264
|
configPath,
|
|
257
265
|
mockSdk,
|
|
258
266
|
openclawPath,
|
|
267
|
+
openclawVersion,
|
|
259
268
|
outDir,
|
|
260
269
|
pluginRoot,
|
|
261
270
|
}) {
|
|
262
271
|
if (configPath) {
|
|
263
272
|
const config = await loadInspectorConfig(configPath, { cwd: pluginRoot });
|
|
264
|
-
const report = await inspectCompatibilityFixtureSet(config, { authorFacing, openclawPath });
|
|
273
|
+
const report = await inspectCompatibilityFixtureSet(config, { authorFacing, openclawPath, openclawVersion });
|
|
265
274
|
await writeCompatibilityReport(report, { cwd: config.rootDir, outDir });
|
|
266
275
|
return {
|
|
267
276
|
report,
|
|
@@ -275,6 +284,7 @@ async function runCiCompatibilityReport({
|
|
|
275
284
|
capture,
|
|
276
285
|
mockSdk,
|
|
277
286
|
openclawPath,
|
|
287
|
+
openclawVersion,
|
|
278
288
|
outDir,
|
|
279
289
|
pluginRoot,
|
|
280
290
|
});
|
|
@@ -314,6 +324,19 @@ function readFlag(commandArgs, name) {
|
|
|
314
324
|
return commandArgs[index + 1] ?? null;
|
|
315
325
|
}
|
|
316
326
|
|
|
327
|
+
function readOpenClawVersion(commandArgs) {
|
|
328
|
+
const index = commandArgs.indexOf("--openclaw-version");
|
|
329
|
+
if (index === -1) return null;
|
|
330
|
+
const version = commandArgs[index + 1];
|
|
331
|
+
if (!version || version.startsWith("-")) {
|
|
332
|
+
throw new Error("--openclaw-version requires a value");
|
|
333
|
+
}
|
|
334
|
+
if (version && (commandArgs.includes("--no-openclaw") || commandArgs.includes("--openclaw"))) {
|
|
335
|
+
throw new Error("--openclaw-version cannot be combined with --openclaw or --no-openclaw");
|
|
336
|
+
}
|
|
337
|
+
return version;
|
|
338
|
+
}
|
|
339
|
+
|
|
317
340
|
function findCaptureEntrypoint(commandArgs) {
|
|
318
341
|
const flagsWithValues = new Set(["--output", "--plugin-root", "--sdk"]);
|
|
319
342
|
const consumedIndexes = new Set();
|
|
@@ -452,13 +475,13 @@ function printHelp() {
|
|
|
452
475
|
|
|
453
476
|
Usage:
|
|
454
477
|
plugin-inspector
|
|
455
|
-
plugin-inspector check [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>
|
|
478
|
+
plugin-inspector check [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path> | --openclaw-version latest|beta|<exact> | --no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--allow-execute] [--author-facing] [--json]
|
|
456
479
|
plugin-inspector config [--plugin-root <path>] [--config <path>] [--json]
|
|
457
480
|
plugin-inspector init [--plugin-root <path>] [--config <path>] [--ci] [--scripts] [--package-manager npm|pnpm|yarn|bun] [--dry-run] [--json] [--force]
|
|
458
|
-
plugin-inspector report --config <path> [--out <dir>] [--openclaw <path>
|
|
459
|
-
plugin-inspector batch <folder> [--out <dir>] [--openclaw <path>
|
|
460
|
-
plugin-inspector inspect [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>
|
|
461
|
-
plugin-inspector ci [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>
|
|
481
|
+
plugin-inspector report --config <path> [--out <dir>] [--openclaw <path> | --openclaw-version latest|beta|<exact> | --no-openclaw] [--author-facing] [--check] [--json]
|
|
482
|
+
plugin-inspector batch <folder> [--out <dir>] [--openclaw <path> | --openclaw-version latest|beta|<exact> | --no-openclaw] [--concurrency <n>] [--keep-plugin-reports] [--author-facing] [--check] [--json]
|
|
483
|
+
plugin-inspector inspect [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path> | --openclaw-version latest|beta|<exact> | --no-openclaw] [--author-facing] [--check] [--json] [--sarif [path]] [--junit [path]] [--allow-execute]
|
|
484
|
+
plugin-inspector ci [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path> | --openclaw-version latest|beta|<exact> | --no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--allow-execute] [--author-facing] [--json] [--no-sarif] [--no-junit]
|
|
462
485
|
plugin-inspector capture <entrypoint> [--mock-sdk|--real-sdk] [--allow-execute] [--plugin-root <path>] [--output <path>]
|
|
463
486
|
|
|
464
487
|
Default check runs from the current plugin root and writes reports/ unless --out is set.
|
|
@@ -351,6 +351,12 @@ function targetOpenClawTable(targetOpenClaw = {}) {
|
|
|
351
351
|
[
|
|
352
352
|
["Configured path", targetOpenClaw.configuredPath ?? "-"],
|
|
353
353
|
["Status", targetOpenClaw.status],
|
|
354
|
+
["Requested version", targetOpenClaw.requestedVersion ?? "-"],
|
|
355
|
+
["Resolved version", targetOpenClaw.version ?? "-"],
|
|
356
|
+
["Range eligibility version", targetOpenClaw.eligibilityVersion ?? "-"],
|
|
357
|
+
["Source", targetOpenClaw.source ? `${targetOpenClaw.source.type}:${targetOpenClaw.source.package}` : "-"],
|
|
358
|
+
["NPM dist-tag", targetOpenClaw.source?.distTag ?? "-"],
|
|
359
|
+
["Prepared cache", targetOpenClaw.cache ? (targetOpenClaw.cache.hit ? "hit" : "miss") : "-"],
|
|
354
360
|
["Compat registry", targetOpenClaw.compatRegistryPath ?? "-"],
|
|
355
361
|
["Compat records", targetOpenClaw.compatRecordCount ?? 0],
|
|
356
362
|
["Compat status counts", Object.entries(statusCounts).map(([status, count]) => `${status}:${count}`).join(", ") || "-"],
|
package/src/config.js
CHANGED
|
@@ -195,7 +195,8 @@ async function readJsonIfExists(filePath) {
|
|
|
195
195
|
if (!existsSync(filePath)) {
|
|
196
196
|
return null;
|
|
197
197
|
}
|
|
198
|
-
|
|
198
|
+
const contents = await readFile(filePath, "utf8");
|
|
199
|
+
return JSON.parse(contents.startsWith("\uFEFF") ? contents.slice(1) : contents);
|
|
199
200
|
}
|
|
200
201
|
|
|
201
202
|
export function packageId(packageName) {
|
package/src/fixture-summary.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readdir } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { compatRecordForIssueCode } from "./contract-probes.js";
|
|
5
5
|
import { readJsonFile } from "./json-file.js";
|
|
6
|
+
import { satisfiesOpenClawCompatibilityRange } from "./openclaw-version.js";
|
|
6
7
|
|
|
7
8
|
const conversationAccessHooks = new Set(["agent_end", "llm_input", "llm_output"]);
|
|
8
9
|
const captureGapRegistrations = new Set([
|
|
@@ -143,6 +144,8 @@ export function summarizePackage(packagePath, packageJson, options = {}) {
|
|
|
143
144
|
extensions: arrayValues(packageJson.openclaw.extensions),
|
|
144
145
|
runtimeExtensions: arrayValues(packageJson.openclaw.runtimeExtensions),
|
|
145
146
|
setupEntry: typeof packageJson.openclaw.setupEntry === "string" ? packageJson.openclaw.setupEntry : null,
|
|
147
|
+
runtimeSetupEntry:
|
|
148
|
+
typeof packageJson.openclaw.runtimeSetupEntry === "string" ? packageJson.openclaw.runtimeSetupEntry : null,
|
|
146
149
|
compatPluginApi:
|
|
147
150
|
typeof packageJson.openclaw.compat?.pluginApi === "string" ? packageJson.openclaw.compat.pluginApi : null,
|
|
148
151
|
buildOpenClawVersion:
|
|
@@ -464,30 +467,36 @@ export function classifyPackageContracts({ fixture, inspection, fixtureReport })
|
|
|
464
467
|
}
|
|
465
468
|
|
|
466
469
|
export function classifyTargetOpenClawCoverage({ fixture, inspection, fixtureReport, targetOpenClaw }) {
|
|
470
|
+
const breakages = [];
|
|
467
471
|
const warnings = [];
|
|
472
|
+
const suggestions = [];
|
|
468
473
|
const logs = [];
|
|
469
474
|
const decisions = [];
|
|
475
|
+
const findingContext = { breakages, warnings, suggestions, fixtureReport, targetOpenClaw };
|
|
470
476
|
|
|
471
|
-
classifyHookNameCoverage({ fixture, inspection, targetOpenClaw,
|
|
472
|
-
classifyRegistrationNameCoverage({ fixture, inspection, targetOpenClaw,
|
|
473
|
-
classifySdkImportCoverage({ fixture, fixtureReport, targetOpenClaw, warnings, logs, decisions });
|
|
477
|
+
classifyHookNameCoverage({ fixture, inspection, targetOpenClaw, logs, findingContext });
|
|
478
|
+
classifyRegistrationNameCoverage({ fixture, inspection, targetOpenClaw, logs, findingContext });
|
|
479
|
+
classifySdkImportCoverage({ fixture, fixtureReport, targetOpenClaw, warnings, logs, decisions, findingContext });
|
|
474
480
|
classifyManifestFieldCoverage({ fixture, fixtureReport, targetOpenClaw, warnings, logs, decisions });
|
|
475
481
|
|
|
476
|
-
return { warnings, logs, decisions };
|
|
482
|
+
return { breakages, warnings, suggestions, logs, decisions };
|
|
477
483
|
}
|
|
478
484
|
|
|
479
485
|
export function classifyCompatibilityFixture({ fixture, inspection, fixtureReport, targetOpenClaw }) {
|
|
486
|
+
const breakages = [];
|
|
480
487
|
const warnings = [];
|
|
481
488
|
const suggestions = [];
|
|
482
489
|
const logs = [];
|
|
483
490
|
const decisions = [];
|
|
484
491
|
|
|
485
492
|
if (inspection.status !== "ok") {
|
|
486
|
-
return { warnings, suggestions, logs, decisions };
|
|
493
|
+
return { breakages, warnings, suggestions, logs, decisions };
|
|
487
494
|
}
|
|
488
495
|
|
|
489
496
|
const targetCoverage = classifyTargetOpenClawCoverage({ fixture, inspection, fixtureReport, targetOpenClaw });
|
|
497
|
+
breakages.push(...targetCoverage.breakages);
|
|
490
498
|
warnings.push(...targetCoverage.warnings);
|
|
499
|
+
suggestions.push(...targetCoverage.suggestions);
|
|
491
500
|
logs.push(...targetCoverage.logs);
|
|
492
501
|
decisions.push(...targetCoverage.decisions);
|
|
493
502
|
|
|
@@ -701,7 +710,7 @@ export function classifyCompatibilityFixture({ fixture, inspection, fixtureRepor
|
|
|
701
710
|
});
|
|
702
711
|
}
|
|
703
712
|
|
|
704
|
-
return { warnings, suggestions, logs, decisions };
|
|
713
|
+
return { breakages, warnings, suggestions, logs, decisions };
|
|
705
714
|
}
|
|
706
715
|
|
|
707
716
|
function classifySdkDeprecations({ fixture, inspection, fixtureReport, warnings, decisions }) {
|
|
@@ -807,7 +816,7 @@ function registrationCaptureGapDetails(inspection, targetOpenClaw) {
|
|
|
807
816
|
return apiRegistrationDetails.filter((registration) => captureGapRegistrations.has(registration.name));
|
|
808
817
|
}
|
|
809
818
|
|
|
810
|
-
function classifyHookNameCoverage({ fixture, inspection, targetOpenClaw,
|
|
819
|
+
function classifyHookNameCoverage({ fixture, inspection, targetOpenClaw, logs, findingContext }) {
|
|
811
820
|
if (targetOpenClaw.status !== "ok" || targetOpenClaw.hookNames.length === 0) {
|
|
812
821
|
return;
|
|
813
822
|
}
|
|
@@ -825,16 +834,18 @@ function classifyHookNameCoverage({ fixture, inspection, targetOpenClaw, warning
|
|
|
825
834
|
return;
|
|
826
835
|
}
|
|
827
836
|
|
|
828
|
-
|
|
837
|
+
addVersionDerivedFinding({
|
|
838
|
+
...findingContext,
|
|
839
|
+
finding: {
|
|
829
840
|
fixture: fixture.id,
|
|
830
841
|
code: "unknown-hook-name",
|
|
831
|
-
level: "warning",
|
|
832
842
|
message: "fixture registers hooks that are not present in the target OpenClaw hook registry",
|
|
833
843
|
evidence: detailEvidence(unknownHooks),
|
|
844
|
+
},
|
|
834
845
|
});
|
|
835
846
|
}
|
|
836
847
|
|
|
837
|
-
function classifyRegistrationNameCoverage({ fixture, inspection, targetOpenClaw,
|
|
848
|
+
function classifyRegistrationNameCoverage({ fixture, inspection, targetOpenClaw, logs, findingContext }) {
|
|
838
849
|
if (targetOpenClaw.status !== "ok" || targetOpenClaw.apiRegistrars.length === 0) {
|
|
839
850
|
return;
|
|
840
851
|
}
|
|
@@ -855,16 +866,18 @@ function classifyRegistrationNameCoverage({ fixture, inspection, targetOpenClaw,
|
|
|
855
866
|
return;
|
|
856
867
|
}
|
|
857
868
|
|
|
858
|
-
|
|
869
|
+
addVersionDerivedFinding({
|
|
870
|
+
...findingContext,
|
|
871
|
+
finding: {
|
|
859
872
|
fixture: fixture.id,
|
|
860
873
|
code: "unknown-registration-name",
|
|
861
|
-
level: "warning",
|
|
862
874
|
message: "fixture calls api.register* methods that are not present in the target OpenClaw plugin API builder",
|
|
863
875
|
evidence: detailEvidence(unknownRegistrations),
|
|
876
|
+
},
|
|
864
877
|
});
|
|
865
878
|
}
|
|
866
879
|
|
|
867
|
-
function classifySdkImportCoverage({ fixture, fixtureReport, targetOpenClaw, warnings, logs, decisions }) {
|
|
880
|
+
function classifySdkImportCoverage({ fixture, fixtureReport, targetOpenClaw, warnings, logs, decisions, findingContext }) {
|
|
868
881
|
if (targetOpenClaw.status !== "ok" || targetOpenClaw.sdkExports.length === 0 || fixtureReport.sdkImports.length === 0) {
|
|
869
882
|
return;
|
|
870
883
|
}
|
|
@@ -888,13 +901,15 @@ function classifySdkImportCoverage({ fixture, fixtureReport, targetOpenClaw, war
|
|
|
888
901
|
}
|
|
889
902
|
|
|
890
903
|
if (unknownImports.length > 0) {
|
|
891
|
-
|
|
904
|
+
addVersionDerivedFinding({
|
|
905
|
+
...findingContext,
|
|
906
|
+
finding: {
|
|
892
907
|
fixture: fixture.id,
|
|
893
908
|
code: "sdk-export-missing",
|
|
894
|
-
level: "warning",
|
|
895
909
|
message: "fixture imports plugin SDK aliases that are not exported by the target OpenClaw package",
|
|
896
910
|
evidence: detailEvidence(unknownImports, "specifier"),
|
|
897
911
|
compatRecord: "plugin-sdk-export-aliases",
|
|
912
|
+
},
|
|
898
913
|
});
|
|
899
914
|
decisions.push({
|
|
900
915
|
fixture: fixture.id,
|
|
@@ -923,6 +938,52 @@ function classifySdkImportCoverage({ fixture, fixtureReport, targetOpenClaw, war
|
|
|
923
938
|
}
|
|
924
939
|
}
|
|
925
940
|
|
|
941
|
+
function addVersionDerivedFinding({ finding, fixtureReport, targetOpenClaw, breakages, warnings, suggestions }) {
|
|
942
|
+
const compatibility = targetCompatibility(fixtureReport, targetOpenClaw);
|
|
943
|
+
if (!compatibility) {
|
|
944
|
+
warnings.push({ ...finding, level: "warning" });
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (compatibility.inDeclaredRange) {
|
|
949
|
+
breakages.push({
|
|
950
|
+
...finding,
|
|
951
|
+
level: "breakage",
|
|
952
|
+
compatibility,
|
|
953
|
+
authorRemediation: {
|
|
954
|
+
summary:
|
|
955
|
+
"Update the plugin to use APIs available in the target OpenClaw version, or narrow its declared compatibility range.",
|
|
956
|
+
},
|
|
957
|
+
authorRemediationDocs: false,
|
|
958
|
+
});
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
suggestions.push({
|
|
963
|
+
...finding,
|
|
964
|
+
level: "information",
|
|
965
|
+
message: `${finding.message}; the resolved target is outside the plugin's declared compatibility range`,
|
|
966
|
+
compatibility,
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function targetCompatibility(fixtureReport, targetOpenClaw) {
|
|
971
|
+
const declaredRange = fixtureReport.package?.openclaw?.compatPluginApi;
|
|
972
|
+
const targetVersion = targetOpenClaw.version;
|
|
973
|
+
const evaluatedVersion = targetOpenClaw.eligibilityVersion ?? targetOpenClaw.version;
|
|
974
|
+
if (!declaredRange || !targetVersion || !evaluatedVersion || !targetOpenClaw.requestedVersion) return null;
|
|
975
|
+
return {
|
|
976
|
+
declaredRange,
|
|
977
|
+
targetVersion,
|
|
978
|
+
evaluatedVersion,
|
|
979
|
+
inDeclaredRange: satisfiesOpenClawCompatibilityRange({
|
|
980
|
+
targetVersion,
|
|
981
|
+
eligibilityVersion: evaluatedVersion,
|
|
982
|
+
range: declaredRange,
|
|
983
|
+
}),
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
|
|
926
987
|
function classifyManifestFieldCoverage({ fixture, fixtureReport, targetOpenClaw, warnings, logs, decisions }) {
|
|
927
988
|
if (targetOpenClaw.status !== "ok" || targetOpenClaw.manifestFields.length === 0) {
|
|
928
989
|
return;
|
|
@@ -984,6 +1045,9 @@ function collectOpenClawEntrypoints(packageDir, openclaw, options) {
|
|
|
984
1045
|
...openclaw.extensions.map((specifier) => ({ kind: "extension", specifier })),
|
|
985
1046
|
...openclaw.runtimeExtensions.map((specifier) => ({ kind: "runtimeExtension", specifier })),
|
|
986
1047
|
...(openclaw.setupEntry ? [{ kind: "setupEntry", specifier: openclaw.setupEntry }] : []),
|
|
1048
|
+
...(openclaw.runtimeSetupEntry
|
|
1049
|
+
? [{ kind: "runtimeSetupEntry", specifier: openclaw.runtimeSetupEntry }]
|
|
1050
|
+
: []),
|
|
987
1051
|
];
|
|
988
1052
|
|
|
989
1053
|
return entrypoints.map((entrypoint) => {
|
|
@@ -1015,7 +1079,7 @@ function hasUsablePackageRuntimeEntrypoint(entrypoint, packageSummary, entrypoin
|
|
|
1015
1079
|
return true;
|
|
1016
1080
|
}
|
|
1017
1081
|
|
|
1018
|
-
if (
|
|
1082
|
+
if (entrypoints.some((candidate) => candidate.exists && isPairedRuntimeEntrypoint(entrypoint, candidate))) {
|
|
1019
1083
|
return true;
|
|
1020
1084
|
}
|
|
1021
1085
|
|
|
@@ -1038,6 +1102,13 @@ function normalizeEntrypointSpecifier(specifier) {
|
|
|
1038
1102
|
return normalized.startsWith("./") ? normalized : `./${normalized}`;
|
|
1039
1103
|
}
|
|
1040
1104
|
|
|
1105
|
+
function isPairedRuntimeEntrypoint(entrypoint, candidate) {
|
|
1106
|
+
return (
|
|
1107
|
+
(entrypoint.kind === "extension" && candidate.kind === "runtimeExtension") ||
|
|
1108
|
+
(entrypoint.kind === "setupEntry" && candidate.kind === "runtimeSetupEntry")
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1041
1112
|
async function findPackageFiles(root, options, depth = 0) {
|
|
1042
1113
|
if (!existsSync(root) || depth > options.maxDepth) {
|
|
1043
1114
|
return [];
|
|
@@ -1227,9 +1298,10 @@ function hasPackagedRuntimeEntrypoint(entrypoint, packageSummary, entrypoints) {
|
|
|
1227
1298
|
}
|
|
1228
1299
|
|
|
1229
1300
|
if (
|
|
1230
|
-
entrypoint.kind === "extension" &&
|
|
1231
1301
|
entrypoints.some(
|
|
1232
|
-
(candidate) =>
|
|
1302
|
+
(candidate) =>
|
|
1303
|
+
isPairedRuntimeEntrypoint(entrypoint, candidate) &&
|
|
1304
|
+
repoPathIncludedInNpmPack(packageSummary, candidate.relativePath),
|
|
1233
1305
|
)
|
|
1234
1306
|
) {
|
|
1235
1307
|
return true;
|
|
@@ -1245,12 +1317,82 @@ function packageMinHostVersionDrift(packageSummary) {
|
|
|
1245
1317
|
if (!nonEmptyString(openclaw?.install?.minHostVersion) || !nonEmptyString(openclaw?.buildOpenClawVersion)) {
|
|
1246
1318
|
return false;
|
|
1247
1319
|
}
|
|
1248
|
-
|
|
1320
|
+
const minimumHostVersion = parseMinHostVersionFloor(openclaw.install.minHostVersion);
|
|
1321
|
+
const buildOpenClawVersion = parseSemver(openclaw.buildOpenClawVersion);
|
|
1322
|
+
if (!minimumHostVersion || !buildOpenClawVersion) {
|
|
1323
|
+
return true;
|
|
1324
|
+
}
|
|
1325
|
+
return compareSemver(minimumHostVersion, buildOpenClawVersion) > 0;
|
|
1249
1326
|
}
|
|
1250
1327
|
|
|
1251
1328
|
function parseMinHostVersionFloor(value) {
|
|
1252
1329
|
const match = /^>=([0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/.exec(value);
|
|
1253
|
-
return match
|
|
1330
|
+
return match ? parseSemver(match[1]) : null;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function parseSemver(value) {
|
|
1334
|
+
const match =
|
|
1335
|
+
/^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(
|
|
1336
|
+
value,
|
|
1337
|
+
);
|
|
1338
|
+
if (!match) {
|
|
1339
|
+
return null;
|
|
1340
|
+
}
|
|
1341
|
+
const coreParts = match.slice(1, 4);
|
|
1342
|
+
if (coreParts.some((part) => part.length > 1 && part.startsWith("0"))) {
|
|
1343
|
+
return null;
|
|
1344
|
+
}
|
|
1345
|
+
const prerelease = match[4]?.split(".") ?? [];
|
|
1346
|
+
if (prerelease.some((part) => /^[0-9]+$/.test(part) && part.length > 1 && part.startsWith("0"))) {
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
return { core: coreParts, prerelease };
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function compareNumericIdentifier(left, right) {
|
|
1353
|
+
if (left.length !== right.length) {
|
|
1354
|
+
return left.length < right.length ? -1 : 1;
|
|
1355
|
+
}
|
|
1356
|
+
if (left === right) {
|
|
1357
|
+
return 0;
|
|
1358
|
+
}
|
|
1359
|
+
return left < right ? -1 : 1;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function compareSemver(left, right) {
|
|
1363
|
+
for (let index = 0; index < left.core.length; index += 1) {
|
|
1364
|
+
const comparison = compareNumericIdentifier(left.core[index], right.core[index]);
|
|
1365
|
+
if (comparison !== 0) {
|
|
1366
|
+
return comparison;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
if (left.prerelease.length === 0 || right.prerelease.length === 0) {
|
|
1370
|
+
if (left.prerelease.length === right.prerelease.length) {
|
|
1371
|
+
return 0;
|
|
1372
|
+
}
|
|
1373
|
+
return left.prerelease.length === 0 ? 1 : -1;
|
|
1374
|
+
}
|
|
1375
|
+
const count = Math.max(left.prerelease.length, right.prerelease.length);
|
|
1376
|
+
for (let index = 0; index < count; index += 1) {
|
|
1377
|
+
const leftPart = left.prerelease[index];
|
|
1378
|
+
const rightPart = right.prerelease[index];
|
|
1379
|
+
if (leftPart === undefined || rightPart === undefined) {
|
|
1380
|
+
return leftPart === undefined ? -1 : 1;
|
|
1381
|
+
}
|
|
1382
|
+
if (leftPart === rightPart) {
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
const leftNumeric = /^[0-9]+$/.test(leftPart);
|
|
1386
|
+
const rightNumeric = /^[0-9]+$/.test(rightPart);
|
|
1387
|
+
if (leftNumeric && rightNumeric) {
|
|
1388
|
+
return compareNumericIdentifier(leftPart, rightPart);
|
|
1389
|
+
}
|
|
1390
|
+
if (leftNumeric !== rightNumeric) {
|
|
1391
|
+
return leftNumeric ? -1 : 1;
|
|
1392
|
+
}
|
|
1393
|
+
return leftPart < rightPart ? -1 : 1;
|
|
1394
|
+
}
|
|
1395
|
+
return 0;
|
|
1254
1396
|
}
|
|
1255
1397
|
|
|
1256
1398
|
function repoPathIncludedInNpmPack(packageSummary, repoPath) {
|