@openclaw/plugin-inspector 0.3.12 → 0.3.13
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 +8 -0
- package/README.md +5 -4
- package/package.json +1 -1
- package/src/advanced.js +1 -0
- package/src/api.js +4 -4
- package/src/batch.js +21 -9
- package/src/cli.js +32 -17
- package/src/compatibility-report.js +13 -0
- package/src/index.js +1 -1
- package/src/inspector.js +1 -1
- package/src/issues.js +183 -0
- package/src/report.js +11 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.13 - 2026-06-09
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Add `authorRemediation.summary` and `authorRemediation.docsUrl` guidance to author-facing compatibility issues and Markdown reports.
|
|
10
|
+
- Add `--author-facing` for `check`, `ci`, and `batch` reports while keeping default output complete for internal coverage findings.
|
|
11
|
+
- Replace the recent `--include-inspector-gaps` option with a clear error pointing to `--author-facing`.
|
|
12
|
+
|
|
5
13
|
## 0.3.12 - 2026-06-09
|
|
6
14
|
|
|
7
15
|
### Changed
|
package/README.md
CHANGED
|
@@ -187,7 +187,7 @@ Common options:
|
|
|
187
187
|
| `--mock-sdk` / `--sdk mock` | Use generated SDK and external-package mocks for runtime capture. |
|
|
188
188
|
| `--real-sdk` / `--sdk real` | Use installed real SDK dependencies instead of mocks. |
|
|
189
189
|
| `--allow-execute` | Permit commands that import plugin code. |
|
|
190
|
-
| `--
|
|
190
|
+
| `--author-facing` | Limit `check`, `ci`, and `batch` reports to findings with `authorRemediation` guidance. |
|
|
191
191
|
| `--json` | Print machine-readable JSON to stdout. |
|
|
192
192
|
| `--sarif [path]` | Write SARIF from `check` or `inspect`; `ci` enables this by default. |
|
|
193
193
|
| `--junit [path]` | Write JUnit XML from `check` or `inspect`; `ci` enables this by default. |
|
|
@@ -307,9 +307,10 @@ Important report sections:
|
|
|
307
307
|
| `logs` | Informational inventory and coverage rows. |
|
|
308
308
|
| `decisions` | Maintainer-facing follow-up or compatibility-policy decisions. |
|
|
309
309
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
Default `check`, `ci`, and `batch` reports include both author-facing and
|
|
311
|
+
internal findings. Pass `--author-facing` when producing plugin-author output;
|
|
312
|
+
that filtered view includes only findings with `authorRemediation.summary` and
|
|
313
|
+
`authorRemediation.docsUrl`.
|
|
313
314
|
|
|
314
315
|
## CI Policy And Shared Reporting Primitives
|
|
315
316
|
|
package/package.json
CHANGED
package/src/advanced.js
CHANGED
package/src/api.js
CHANGED
|
@@ -43,8 +43,8 @@ export async function loadPluginConfig(options = {}) {
|
|
|
43
43
|
export async function inspectPluginRoot(options = {}) {
|
|
44
44
|
const config = await loadPluginConfig(options);
|
|
45
45
|
return inspectCompatibilityFixtureSet(config, {
|
|
46
|
+
authorFacing: options.authorFacing,
|
|
46
47
|
generatedAt: options.generatedAt,
|
|
47
|
-
includeInspectorGaps: options.includeInspectorGaps,
|
|
48
48
|
openclawPath: options.openclawPath,
|
|
49
49
|
executionResults: options.executionResults,
|
|
50
50
|
targetOpenClaw: options.targetOpenClaw,
|
|
@@ -59,8 +59,8 @@ export async function inspectFixtureSetConfig(options = {}) {
|
|
|
59
59
|
export async function inspectCompatibilityFixtureSetConfig(options = {}) {
|
|
60
60
|
const config = await loadFixtureSetConfig(options);
|
|
61
61
|
return inspectCompatibilityFixtureSet(config, {
|
|
62
|
+
authorFacing: options.authorFacing,
|
|
62
63
|
generatedAt: options.generatedAt,
|
|
63
|
-
includeInspectorGaps: options.includeInspectorGaps,
|
|
64
64
|
openclawPath: options.openclawPath,
|
|
65
65
|
executionResults: options.executionResults,
|
|
66
66
|
targetOpenClaw: options.targetOpenClaw,
|
|
@@ -114,8 +114,8 @@ export async function buildFixtureSetColdImportReadiness(options = {}) {
|
|
|
114
114
|
const report =
|
|
115
115
|
options.report ??
|
|
116
116
|
(await inspectCompatibilityFixtureSet(config, {
|
|
117
|
+
authorFacing: options.authorFacing,
|
|
117
118
|
generatedAt: options.generatedAt,
|
|
118
|
-
includeInspectorGaps: options.includeInspectorGaps,
|
|
119
119
|
openclawPath: options.openclawPath,
|
|
120
120
|
executionResults: options.executionResults,
|
|
121
121
|
targetOpenClaw: options.targetOpenClaw,
|
|
@@ -147,8 +147,8 @@ export async function buildFixtureSetWorkspacePlan(options = {}) {
|
|
|
147
147
|
const report =
|
|
148
148
|
options.report ??
|
|
149
149
|
(await inspectCompatibilityFixtureSet(config, {
|
|
150
|
+
authorFacing: options.authorFacing,
|
|
150
151
|
generatedAt: options.generatedAt,
|
|
151
|
-
includeInspectorGaps: options.includeInspectorGaps,
|
|
152
152
|
openclawPath: options.openclawPath,
|
|
153
153
|
executionResults: options.executionResults,
|
|
154
154
|
targetOpenClaw: options.targetOpenClaw,
|
package/src/batch.js
CHANGED
|
@@ -108,9 +108,9 @@ async function inspectBatchPlugin(pluginRoot, options) {
|
|
|
108
108
|
try {
|
|
109
109
|
const { report } = await runPluginCheck({
|
|
110
110
|
allowExecution: options.allowExecution,
|
|
111
|
+
authorFacing: options.authorFacing,
|
|
111
112
|
capture: options.capture,
|
|
112
113
|
configPath: options.configPath,
|
|
113
|
-
includeInspectorGaps: options.includeInspectorGaps,
|
|
114
114
|
mockSdk: options.mockSdk,
|
|
115
115
|
openclawPath: options.openclawPath,
|
|
116
116
|
outDir: options.outDir,
|
|
@@ -148,17 +148,20 @@ async function inspectBatchPlugin(pluginRoot, options) {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
function normalizeReportFindings(report) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
finding,
|
|
156
|
-
finding.status === "blocking" || finding.severity === "P0" ? "error" : "warning",
|
|
157
|
-
),
|
|
151
|
+
const issueFindings = (report.issues ?? []).map((finding) =>
|
|
152
|
+
normalizeFinding(
|
|
153
|
+
finding,
|
|
154
|
+
finding.status === "blocking" || finding.severity === "P0" ? "error" : "warning",
|
|
158
155
|
),
|
|
156
|
+
);
|
|
157
|
+
const issueKeys = new Set(issueFindings.map(findingKey));
|
|
158
|
+
const rawFindings = [
|
|
159
|
+
...(report.breakages ?? []).map((finding) => normalizeFinding(finding, "error")),
|
|
159
160
|
...(report.warnings ?? []).map((finding) => normalizeFinding(finding, "warning")),
|
|
160
161
|
...(report.suggestions ?? []).map((finding) => normalizeFinding(finding, "warning")),
|
|
161
|
-
];
|
|
162
|
+
].filter((finding) => !issueKeys.has(findingKey(finding)));
|
|
163
|
+
|
|
164
|
+
return [...issueFindings, ...rawFindings];
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
function normalizeFinding(finding, kind) {
|
|
@@ -169,9 +172,18 @@ function normalizeFinding(finding, kind) {
|
|
|
169
172
|
issueClass: finding.issueClass,
|
|
170
173
|
message: finding.message ?? finding.title ?? "See plugin report.",
|
|
171
174
|
evidence: finding.evidence,
|
|
175
|
+
...(finding.authorRemediation ? { authorRemediation: finding.authorRemediation } : {}),
|
|
172
176
|
};
|
|
173
177
|
}
|
|
174
178
|
|
|
179
|
+
function findingKey(finding) {
|
|
180
|
+
return [
|
|
181
|
+
finding.fixture ?? "",
|
|
182
|
+
finding.code ?? "",
|
|
183
|
+
...(Array.isArray(finding.evidence) ? finding.evidence : []),
|
|
184
|
+
].join("\n");
|
|
185
|
+
}
|
|
186
|
+
|
|
175
187
|
function findingFrequencyRows(entries) {
|
|
176
188
|
const byCode = new Map();
|
|
177
189
|
for (const entry of entries) {
|
package/src/cli.js
CHANGED
|
@@ -64,7 +64,7 @@ async function runBatch(commandArgs) {
|
|
|
64
64
|
const json = commandArgs.includes("--json");
|
|
65
65
|
const check = commandArgs.includes("--check");
|
|
66
66
|
const keepPluginReports = commandArgs.includes("--keep-plugin-reports");
|
|
67
|
-
const
|
|
67
|
+
const authorFacing = readAuthorFacingFlag(commandArgs);
|
|
68
68
|
if (!inputDir) {
|
|
69
69
|
throw new Error("batch requires a folder of plugin roots");
|
|
70
70
|
}
|
|
@@ -73,7 +73,7 @@ async function runBatch(commandArgs) {
|
|
|
73
73
|
outDir,
|
|
74
74
|
openclawPath,
|
|
75
75
|
concurrency,
|
|
76
|
-
|
|
76
|
+
authorFacing,
|
|
77
77
|
keepPluginReports,
|
|
78
78
|
});
|
|
79
79
|
|
|
@@ -110,12 +110,12 @@ async function runCheck(commandArgs) {
|
|
|
110
110
|
const mockSdk = readMockSdkFlag(commandArgs);
|
|
111
111
|
const allowExecution = readAllowExecutionFlag(commandArgs);
|
|
112
112
|
const ciOutputs = readCiOutputFlags(commandArgs);
|
|
113
|
-
const
|
|
113
|
+
const authorFacing = readAuthorFacingFlag(commandArgs);
|
|
114
114
|
const { report, paths } = await runPluginCheck({
|
|
115
115
|
allowExecution,
|
|
116
|
+
authorFacing,
|
|
116
117
|
capture,
|
|
117
118
|
configPath,
|
|
118
|
-
includeInspectorGaps,
|
|
119
119
|
mockSdk,
|
|
120
120
|
openclawPath,
|
|
121
121
|
outDir,
|
|
@@ -168,12 +168,18 @@ async function runInit(commandArgs) {
|
|
|
168
168
|
async function runReport(command, commandArgs) {
|
|
169
169
|
const configPath = readFlag(commandArgs, "--config");
|
|
170
170
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
171
|
+
const openclawPath = commandArgs.includes("--no-openclaw") ? false : readFlag(commandArgs, "--openclaw");
|
|
171
172
|
const check = commandArgs.includes("--check") || command === "ci";
|
|
172
173
|
const json = commandArgs.includes("--json");
|
|
173
174
|
const ciOutputs = readCiOutputFlags(commandArgs);
|
|
175
|
+
const authorFacing = readAuthorFacingFlag(commandArgs);
|
|
174
176
|
const config = await loadInspectorConfig(configPath);
|
|
175
|
-
const report =
|
|
176
|
-
|
|
177
|
+
const report = authorFacing
|
|
178
|
+
? await inspectCompatibilityFixtureSet(config, { authorFacing, openclawPath })
|
|
179
|
+
: await inspectFixtureSet(config);
|
|
180
|
+
const paths = authorFacing
|
|
181
|
+
? await writeCompatibilityReport(report, { cwd: config.rootDir, outDir })
|
|
182
|
+
: await writeReport(report, { outDir });
|
|
177
183
|
await writeCiOutputArtifacts(report, {
|
|
178
184
|
...ciOutputs,
|
|
179
185
|
cwd: path.dirname(paths.jsonPath),
|
|
@@ -201,12 +207,12 @@ async function runCi(commandArgs) {
|
|
|
201
207
|
const mockSdk = readMockSdkFlag(commandArgs);
|
|
202
208
|
const allowExecution = readAllowExecutionFlag(commandArgs);
|
|
203
209
|
const ciOutputs = readCiOutputFlags(commandArgs, { defaultEnabled: true });
|
|
204
|
-
const
|
|
210
|
+
const authorFacing = readAuthorFacingFlag(commandArgs);
|
|
205
211
|
const { report, reportDir } = await runCiCompatibilityReport({
|
|
206
212
|
allowExecution,
|
|
213
|
+
authorFacing,
|
|
207
214
|
capture,
|
|
208
215
|
configPath,
|
|
209
|
-
includeInspectorGaps,
|
|
210
216
|
mockSdk,
|
|
211
217
|
openclawPath,
|
|
212
218
|
outDir,
|
|
@@ -245,9 +251,9 @@ async function runCi(commandArgs) {
|
|
|
245
251
|
|
|
246
252
|
async function runCiCompatibilityReport({
|
|
247
253
|
allowExecution,
|
|
254
|
+
authorFacing,
|
|
248
255
|
capture,
|
|
249
256
|
configPath,
|
|
250
|
-
includeInspectorGaps,
|
|
251
257
|
mockSdk,
|
|
252
258
|
openclawPath,
|
|
253
259
|
outDir,
|
|
@@ -255,7 +261,7 @@ async function runCiCompatibilityReport({
|
|
|
255
261
|
}) {
|
|
256
262
|
if (configPath) {
|
|
257
263
|
const config = await loadInspectorConfig(configPath, { cwd: pluginRoot });
|
|
258
|
-
const report = await inspectCompatibilityFixtureSet(config, {
|
|
264
|
+
const report = await inspectCompatibilityFixtureSet(config, { authorFacing, openclawPath });
|
|
259
265
|
await writeCompatibilityReport(report, { cwd: config.rootDir, outDir });
|
|
260
266
|
return {
|
|
261
267
|
report,
|
|
@@ -265,8 +271,8 @@ async function runCiCompatibilityReport({
|
|
|
265
271
|
|
|
266
272
|
const { report } = await runPluginCheck({
|
|
267
273
|
allowExecution,
|
|
274
|
+
authorFacing,
|
|
268
275
|
capture,
|
|
269
|
-
includeInspectorGaps,
|
|
270
276
|
mockSdk,
|
|
271
277
|
openclawPath,
|
|
272
278
|
outDir,
|
|
@@ -373,6 +379,15 @@ function readAllowExecutionFlag(commandArgs) {
|
|
|
373
379
|
return commandArgs.includes("--allow-execute");
|
|
374
380
|
}
|
|
375
381
|
|
|
382
|
+
function readAuthorFacingFlag(commandArgs) {
|
|
383
|
+
if (commandArgs.includes("--include-inspector-gaps")) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
"--include-inspector-gaps has been replaced by --author-facing; default output now includes internal findings.",
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
return commandArgs.includes("--author-facing");
|
|
389
|
+
}
|
|
390
|
+
|
|
376
391
|
function renderCiTextSummary(summary) {
|
|
377
392
|
return [
|
|
378
393
|
`Status: ${summary.status.toUpperCase()}`,
|
|
@@ -437,18 +452,18 @@ function printHelp() {
|
|
|
437
452
|
|
|
438
453
|
Usage:
|
|
439
454
|
plugin-inspector
|
|
440
|
-
plugin-inspector check [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--allow-execute] [--
|
|
455
|
+
plugin-inspector check [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--allow-execute] [--author-facing] [--json]
|
|
441
456
|
plugin-inspector config [--plugin-root <path>] [--config <path>] [--json]
|
|
442
457
|
plugin-inspector init [--plugin-root <path>] [--config <path>] [--ci] [--scripts] [--package-manager npm|pnpm|yarn|bun] [--dry-run] [--json] [--force]
|
|
443
|
-
plugin-inspector report --config <path> [--out <dir>] [--check] [--json]
|
|
444
|
-
plugin-inspector batch <folder> [--out <dir>] [--openclaw <path>] [--no-openclaw] [--concurrency <n>] [--keep-plugin-reports] [--
|
|
445
|
-
plugin-inspector inspect [--plugin-root <path>] [--config <path>] [--out <dir>] [--check] [--json] [--sarif [path]] [--junit [path]] [--allow-execute]
|
|
446
|
-
plugin-inspector ci [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--allow-execute] [--
|
|
458
|
+
plugin-inspector report --config <path> [--out <dir>] [--openclaw <path>] [--no-openclaw] [--author-facing] [--check] [--json]
|
|
459
|
+
plugin-inspector batch <folder> [--out <dir>] [--openclaw <path>] [--no-openclaw] [--concurrency <n>] [--keep-plugin-reports] [--author-facing] [--check] [--json]
|
|
460
|
+
plugin-inspector inspect [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--author-facing] [--check] [--json] [--sarif [path]] [--junit [path]] [--allow-execute]
|
|
461
|
+
plugin-inspector ci [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--allow-execute] [--author-facing] [--json] [--no-sarif] [--no-junit]
|
|
447
462
|
plugin-inspector capture <entrypoint> [--mock-sdk|--real-sdk] [--allow-execute] [--plugin-root <path>] [--output <path>]
|
|
448
463
|
|
|
449
464
|
Default check runs from the current plugin root and writes reports/ unless --out is set.
|
|
450
465
|
CI writes SARIF and JUnit artifacts by default; check/inspect can write them with --sarif and --junit.
|
|
451
466
|
Runtime capture is opt-in because it imports plugin code; use --runtime with --allow-execute or PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1.
|
|
452
|
-
|
|
467
|
+
Default output includes author-facing and internal findings; pass --author-facing to show only findings with author remediation docs.
|
|
453
468
|
`);
|
|
454
469
|
}
|
|
@@ -250,10 +250,23 @@ function issueBlock(issue, options) {
|
|
|
250
250
|
` - state: ${issueState(issue)}`,
|
|
251
251
|
" - evidence:",
|
|
252
252
|
...evidenceList(issue.evidence, options).map((item) => ` - ${item}`),
|
|
253
|
+
...remediationList(issue),
|
|
253
254
|
...runtimeCoverageList(issue, options),
|
|
254
255
|
].join("\n");
|
|
255
256
|
}
|
|
256
257
|
|
|
258
|
+
function remediationList(issue) {
|
|
259
|
+
const remediation = issue.authorRemediation;
|
|
260
|
+
if (!remediation?.summary) {
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
return [
|
|
264
|
+
" - author remediation:",
|
|
265
|
+
` - ${remediation.summary}`,
|
|
266
|
+
` - docs: ${remediation.docsUrl}`,
|
|
267
|
+
];
|
|
268
|
+
}
|
|
269
|
+
|
|
257
270
|
function issueState(issue) {
|
|
258
271
|
const flags = [
|
|
259
272
|
issue.status,
|
package/src/index.js
CHANGED
|
@@ -217,7 +217,7 @@ export {
|
|
|
217
217
|
validateImportLoopProfile,
|
|
218
218
|
writeImportLoopProfile,
|
|
219
219
|
} from "./import-loop-profile.js";
|
|
220
|
-
export { classifyIssueFinding, issueId, knownIssueCodes } from "./issues.js";
|
|
220
|
+
export { classifyIssueFinding, issueId, isAuthorFacingFinding, knownIssueCodes } from "./issues.js";
|
|
221
221
|
export { inspectFixtureSet, inspectPlugin, inspectSourceText } from "./inspector.js";
|
|
222
222
|
export { openClawTargetPathCandidates, readOpenClawTargetSurface } from "./openclaw-target.js";
|
|
223
223
|
export {
|
package/src/inspector.js
CHANGED
|
@@ -35,9 +35,9 @@ export async function inspectCompatibilityFixtureSet(config, options = {}) {
|
|
|
35
35
|
config,
|
|
36
36
|
inspections,
|
|
37
37
|
failures,
|
|
38
|
+
authorFacing: options.authorFacing,
|
|
38
39
|
generatedAt: options.generatedAt,
|
|
39
40
|
executionResults: options.executionResults,
|
|
40
|
-
includeInspectorGaps: options.includeInspectorGaps,
|
|
41
41
|
targetOpenClaw,
|
|
42
42
|
buildFixtureReport: ({ fixture, inspection }) =>
|
|
43
43
|
buildCompatibilityFixtureReport({
|
package/src/issues.js
CHANGED
|
@@ -45,6 +45,12 @@ export const knownIssueCodes = new Set([
|
|
|
45
45
|
"unrecognized-security-manifest",
|
|
46
46
|
]);
|
|
47
47
|
|
|
48
|
+
const authorRemediationDocsUrl = (code) => `https://docs.openclaw.ai/clawhub/plugin-validation-fixes#${code}`;
|
|
49
|
+
|
|
50
|
+
const authorRemediation = (summary) => ({ summary });
|
|
51
|
+
|
|
52
|
+
const migrationRemediation = authorRemediation;
|
|
53
|
+
|
|
48
54
|
export const issueMetadataByCode = {
|
|
49
55
|
"before-tool-call-probe": {
|
|
50
56
|
severity: "P1",
|
|
@@ -63,6 +69,13 @@ export const issueMetadataByCode = {
|
|
|
63
69
|
owner: "core",
|
|
64
70
|
decision: "core-compat-adapter",
|
|
65
71
|
title: "channelEnvVars legacy manifest metadata must stay covered",
|
|
72
|
+
authorRemediation: migrationRemediation(
|
|
73
|
+
"Move legacy channel environment variable metadata into the current setup/config metadata while keeping the old field until your supported OpenClaw range no longer needs it.",
|
|
74
|
+
[
|
|
75
|
+
"Mirror each channel environment variable into the current setup or provider configuration metadata.",
|
|
76
|
+
"Keep channelEnvVars only as backwards compatibility for older OpenClaw versions you still support.",
|
|
77
|
+
],
|
|
78
|
+
),
|
|
66
79
|
},
|
|
67
80
|
"conversation-access-hook": {
|
|
68
81
|
severity: "P1",
|
|
@@ -75,12 +88,27 @@ export const issueMetadataByCode = {
|
|
|
75
88
|
owner: "core",
|
|
76
89
|
decision: "core-compat-adapter",
|
|
77
90
|
title: "legacy before_agent_start hook compatibility is still used",
|
|
91
|
+
authorRemediation: migrationRemediation(
|
|
92
|
+
"Replace the legacy before_agent_start hook with the current prompt/model hooks.",
|
|
93
|
+
[
|
|
94
|
+
"Move model-selection work to before_model_resolve when possible.",
|
|
95
|
+
"Move prompt mutation work to before_prompt_build.",
|
|
96
|
+
"Keep before_agent_start only if your declared compatibility range still includes OpenClaw versions that require it.",
|
|
97
|
+
],
|
|
98
|
+
),
|
|
78
99
|
},
|
|
79
100
|
"legacy-root-sdk-import": {
|
|
80
101
|
severity: "P2",
|
|
81
102
|
owner: "core",
|
|
82
103
|
decision: "core-compat-adapter",
|
|
83
104
|
title: "root plugin SDK barrel is still used by fixtures",
|
|
105
|
+
authorRemediation: migrationRemediation(
|
|
106
|
+
"Prefer focused public plugin SDK subpath imports instead of the legacy root barrel.",
|
|
107
|
+
[
|
|
108
|
+
"Replace imports from openclaw/plugin-sdk with the documented subpath for the API you use.",
|
|
109
|
+
"Keep the root import only while supporting older OpenClaw versions that do not expose the subpath.",
|
|
110
|
+
],
|
|
111
|
+
),
|
|
84
112
|
},
|
|
85
113
|
"sdk-export-missing": {
|
|
86
114
|
severity: "P1",
|
|
@@ -93,12 +121,26 @@ export const issueMetadataByCode = {
|
|
|
93
121
|
owner: "plugin",
|
|
94
122
|
decision: "plugin-upstream-fix",
|
|
95
123
|
title: "plugin imports reserved bundled-plugin SDK compatibility subpaths",
|
|
124
|
+
authorRemediation: authorRemediation(
|
|
125
|
+
"Stop importing reserved bundled-plugin SDK compatibility paths.",
|
|
126
|
+
[
|
|
127
|
+
"Replace reserved OpenClaw internal SDK imports with documented public openclaw/plugin-sdk subpaths.",
|
|
128
|
+
"If no public API exists for the behavior, vendor a plugin-local helper or request a public OpenClaw API.",
|
|
129
|
+
],
|
|
130
|
+
),
|
|
96
131
|
},
|
|
97
132
|
"security-manifest-schema-unavailable": {
|
|
98
133
|
severity: "P3",
|
|
99
134
|
owner: "plugin",
|
|
100
135
|
decision: "plugin-upstream-fix",
|
|
101
136
|
title: "plugin security manifest references an unavailable schema",
|
|
137
|
+
authorRemediation: authorRemediation(
|
|
138
|
+
"Remove or update the unsupported security manifest schema reference.",
|
|
139
|
+
[
|
|
140
|
+
"Delete the schema URL from openclaw.security.json if it is advisory-only.",
|
|
141
|
+
"Use a documented versioned schema once OpenClaw publishes one.",
|
|
142
|
+
],
|
|
143
|
+
),
|
|
102
144
|
},
|
|
103
145
|
"missing-compat-record": {
|
|
104
146
|
severity: "P1",
|
|
@@ -117,18 +159,37 @@ export const issueMetadataByCode = {
|
|
|
117
159
|
owner: "plugin",
|
|
118
160
|
decision: "plugin-upstream-fix",
|
|
119
161
|
title: "manifest display name is missing",
|
|
162
|
+
authorRemediation: authorRemediation(
|
|
163
|
+
"Add a display name to the plugin manifest.",
|
|
164
|
+
["Set a non-empty name field in openclaw.plugin.json."],
|
|
165
|
+
'{\n "name": "My Plugin"\n}',
|
|
166
|
+
),
|
|
120
167
|
},
|
|
121
168
|
"manifest-unknown-contracts": {
|
|
122
169
|
severity: "P1",
|
|
123
170
|
owner: "plugin",
|
|
124
171
|
decision: "plugin-upstream-fix",
|
|
125
172
|
title: "manifest declares unsupported contract keys",
|
|
173
|
+
authorRemediation: authorRemediation(
|
|
174
|
+
"Remove unsupported manifest contract keys or move them to a documented OpenClaw contract field.",
|
|
175
|
+
[
|
|
176
|
+
"Compare the contracts object to the OpenClaw manifest fields supported by your target version.",
|
|
177
|
+
"Delete custom contract keys unless OpenClaw has a versioned schema for them.",
|
|
178
|
+
],
|
|
179
|
+
),
|
|
126
180
|
},
|
|
127
181
|
"manifest-unknown-fields": {
|
|
128
182
|
severity: "P2",
|
|
129
183
|
owner: "plugin",
|
|
130
184
|
decision: "plugin-upstream-fix",
|
|
131
185
|
title: "manifest uses unsupported top-level fields",
|
|
186
|
+
authorRemediation: authorRemediation(
|
|
187
|
+
"Move unsupported top-level manifest fields into supported package metadata or remove them.",
|
|
188
|
+
[
|
|
189
|
+
"Keep openclaw.plugin.json limited to fields supported by the target OpenClaw manifest schema.",
|
|
190
|
+
"Move package-level metadata into package.json openclaw metadata when that field is supported.",
|
|
191
|
+
],
|
|
192
|
+
),
|
|
132
193
|
},
|
|
133
194
|
"package-build-artifact-entrypoint": {
|
|
134
195
|
severity: "P2",
|
|
@@ -147,72 +208,159 @@ export const issueMetadataByCode = {
|
|
|
147
208
|
owner: "plugin",
|
|
148
209
|
decision: "plugin-upstream-fix",
|
|
149
210
|
title: "OpenClaw package entrypoint is missing",
|
|
211
|
+
authorRemediation: authorRemediation(
|
|
212
|
+
"Publish the entrypoint declared in OpenClaw package metadata or update the metadata to point at an existing file.",
|
|
213
|
+
[
|
|
214
|
+
"Check package.json openclaw.extensions and openclaw.runtimeExtensions.",
|
|
215
|
+
"Ensure the referenced file exists in the published artifact, usually under dist/ after build.",
|
|
216
|
+
],
|
|
217
|
+
),
|
|
150
218
|
},
|
|
151
219
|
"package-install-metadata-incomplete": {
|
|
152
220
|
severity: "P2",
|
|
153
221
|
owner: "plugin",
|
|
154
222
|
decision: "plugin-upstream-fix",
|
|
155
223
|
title: "OpenClaw package install metadata is incomplete",
|
|
224
|
+
authorRemediation: authorRemediation(
|
|
225
|
+
"Complete the OpenClaw install metadata so ClawHub can identify the install target.",
|
|
226
|
+
[
|
|
227
|
+
"Fill package.json openclaw.install with the supported release target.",
|
|
228
|
+
"Align clawhubSpec, npmSpec, and defaultChoice with the package you publish.",
|
|
229
|
+
],
|
|
230
|
+
),
|
|
156
231
|
},
|
|
157
232
|
"package-json-missing": {
|
|
158
233
|
severity: "P2",
|
|
159
234
|
owner: "plugin",
|
|
160
235
|
decision: "plugin-upstream-fix",
|
|
161
236
|
title: "package metadata is missing",
|
|
237
|
+
authorRemediation: authorRemediation(
|
|
238
|
+
"Add a package.json to the plugin package.",
|
|
239
|
+
[
|
|
240
|
+
"Include the package name and version.",
|
|
241
|
+
"Add an openclaw metadata block describing extensions, compatibility, and install details.",
|
|
242
|
+
],
|
|
243
|
+
),
|
|
162
244
|
},
|
|
163
245
|
"package-manifest-version-drift": {
|
|
164
246
|
severity: "P2",
|
|
165
247
|
owner: "plugin",
|
|
166
248
|
decision: "plugin-upstream-fix",
|
|
167
249
|
title: "package and manifest versions drift",
|
|
250
|
+
authorRemediation: authorRemediation(
|
|
251
|
+
"Align the plugin version declared in package.json and openclaw.plugin.json.",
|
|
252
|
+
[
|
|
253
|
+
"Use the same version in both files, or remove stale manifest version metadata if package.json is authoritative.",
|
|
254
|
+
"Republish with a new package version after changing published metadata.",
|
|
255
|
+
],
|
|
256
|
+
),
|
|
168
257
|
},
|
|
169
258
|
"package-min-host-version-drift": {
|
|
170
259
|
severity: "P2",
|
|
171
260
|
owner: "plugin",
|
|
172
261
|
decision: "plugin-upstream-fix",
|
|
173
262
|
title: "OpenClaw package minimum host version drifts from build target",
|
|
263
|
+
authorRemediation: authorRemediation(
|
|
264
|
+
"Set the package minimum host version to the OpenClaw version range the plugin was built and tested against.",
|
|
265
|
+
[
|
|
266
|
+
"Update package.json openclaw.install.minHostVersion or compatibility metadata.",
|
|
267
|
+
"Keep it semver-compatible with the target OpenClaw build version.",
|
|
268
|
+
],
|
|
269
|
+
),
|
|
174
270
|
},
|
|
175
271
|
"package-npm-pack-entrypoint-missing": {
|
|
176
272
|
severity: "P1",
|
|
177
273
|
owner: "plugin",
|
|
178
274
|
decision: "plugin-upstream-fix",
|
|
179
275
|
title: "advertised npm artifact is missing OpenClaw entrypoints",
|
|
276
|
+
authorRemediation: authorRemediation(
|
|
277
|
+
"Include the declared OpenClaw entrypoints in the npm-packed artifact.",
|
|
278
|
+
[
|
|
279
|
+
"Run npm pack locally and inspect the tarball contents.",
|
|
280
|
+
"Update package.json files so dist files and manifests are included.",
|
|
281
|
+
"Build before packing if the entrypoint is generated.",
|
|
282
|
+
],
|
|
283
|
+
),
|
|
180
284
|
},
|
|
181
285
|
"package-npm-pack-metadata-missing": {
|
|
182
286
|
severity: "P2",
|
|
183
287
|
owner: "plugin",
|
|
184
288
|
decision: "plugin-upstream-fix",
|
|
185
289
|
title: "advertised npm artifact is missing OpenClaw metadata",
|
|
290
|
+
authorRemediation: authorRemediation(
|
|
291
|
+
"Include OpenClaw metadata files in the npm-packed artifact.",
|
|
292
|
+
[
|
|
293
|
+
"Run npm pack locally and inspect package.json and OpenClaw manifest files.",
|
|
294
|
+
"Update package.json files so required metadata is not excluded.",
|
|
295
|
+
],
|
|
296
|
+
),
|
|
186
297
|
},
|
|
187
298
|
"package-npm-pack-unavailable": {
|
|
188
299
|
severity: "P1",
|
|
189
300
|
owner: "plugin",
|
|
190
301
|
decision: "plugin-upstream-fix",
|
|
191
302
|
title: "advertised npm artifact cannot be packed",
|
|
303
|
+
authorRemediation: authorRemediation(
|
|
304
|
+
"Make the package packable before publishing it through ClawHub.",
|
|
305
|
+
[
|
|
306
|
+
"Remove private:true if this package is intended to publish.",
|
|
307
|
+
"Ensure package.json has a valid name and version.",
|
|
308
|
+
"Fix package scripts or files entries that make npm pack fail.",
|
|
309
|
+
],
|
|
310
|
+
),
|
|
192
311
|
},
|
|
193
312
|
"package-openclaw-entry-missing": {
|
|
194
313
|
severity: "P2",
|
|
195
314
|
owner: "plugin",
|
|
196
315
|
decision: "plugin-upstream-fix",
|
|
197
316
|
title: "OpenClaw package entrypoint metadata is missing",
|
|
317
|
+
authorRemediation: authorRemediation(
|
|
318
|
+
"Declare the plugin runtime entrypoint in package.json OpenClaw metadata.",
|
|
319
|
+
[
|
|
320
|
+
"Add openclaw.extensions for extension entrypoints.",
|
|
321
|
+
"Add openclaw.runtimeExtensions when the plugin has runtime-side code.",
|
|
322
|
+
],
|
|
323
|
+
),
|
|
198
324
|
},
|
|
199
325
|
"package-openclaw-metadata-missing": {
|
|
200
326
|
severity: "P2",
|
|
201
327
|
owner: "plugin",
|
|
202
328
|
decision: "plugin-upstream-fix",
|
|
203
329
|
title: "OpenClaw package metadata is missing",
|
|
330
|
+
authorRemediation: authorRemediation(
|
|
331
|
+
"Add the package.json openclaw metadata block.",
|
|
332
|
+
[
|
|
333
|
+
"Describe extension entrypoints, plugin API compatibility, and install metadata.",
|
|
334
|
+
"Keep package metadata in sync with openclaw.plugin.json when both files are present.",
|
|
335
|
+
],
|
|
336
|
+
),
|
|
204
337
|
},
|
|
205
338
|
"package-openclaw-unsupported-metadata": {
|
|
206
339
|
severity: "P2",
|
|
207
340
|
owner: "plugin",
|
|
208
341
|
decision: "plugin-upstream-fix",
|
|
209
342
|
title: "package declares unsupported OpenClaw metadata",
|
|
343
|
+
authorRemediation: authorRemediation(
|
|
344
|
+
"Remove unsupported OpenClaw package metadata fields.",
|
|
345
|
+
[
|
|
346
|
+
"Delete openclaw.bundle and other fields not accepted by the current package schema.",
|
|
347
|
+
"Move bundle-specific data to documented manifest fields when available.",
|
|
348
|
+
],
|
|
349
|
+
),
|
|
210
350
|
},
|
|
211
351
|
"package-plugin-api-compat-missing": {
|
|
212
352
|
severity: "P2",
|
|
213
353
|
owner: "plugin",
|
|
214
354
|
decision: "plugin-upstream-fix",
|
|
215
355
|
title: "plugin API compatibility range is missing",
|
|
356
|
+
authorRemediation: authorRemediation(
|
|
357
|
+
"Declare the OpenClaw plugin API range this package supports.",
|
|
358
|
+
[
|
|
359
|
+
"Add package.json `openclaw.compat.pluginApi` with the OpenClaw plugin API range you tested.",
|
|
360
|
+
"If known, include the OpenClaw build/version used to produce the package metadata.",
|
|
361
|
+
],
|
|
362
|
+
'"openclaw": {\n "compat": {\n "pluginApi": ">=0.1.0"\n }\n}',
|
|
363
|
+
),
|
|
216
364
|
},
|
|
217
365
|
"package-typescript-source-entrypoint": {
|
|
218
366
|
severity: "P2",
|
|
@@ -225,6 +373,13 @@ export const issueMetadataByCode = {
|
|
|
225
373
|
owner: "core",
|
|
226
374
|
decision: "core-compat-adapter",
|
|
227
375
|
title: "providerAuthEnvVars legacy manifest metadata must stay covered",
|
|
376
|
+
authorRemediation: migrationRemediation(
|
|
377
|
+
"Move legacy provider authentication environment variables into current provider setup metadata.",
|
|
378
|
+
[
|
|
379
|
+
"Mirror providerAuthEnvVars into setup.providers[].envVars or the current provider-choice metadata.",
|
|
380
|
+
"Keep the legacy field only while supporting older OpenClaw versions that still read it.",
|
|
381
|
+
],
|
|
382
|
+
),
|
|
228
383
|
},
|
|
229
384
|
"registration-capture-gap": {
|
|
230
385
|
severity: "P2",
|
|
@@ -255,6 +410,13 @@ export const issueMetadataByCode = {
|
|
|
255
410
|
owner: "plugin",
|
|
256
411
|
decision: "plugin-upstream-fix",
|
|
257
412
|
title: "plugin ships an unsupported security manifest",
|
|
413
|
+
authorRemediation: authorRemediation(
|
|
414
|
+
"Remove unsupported security manifest files until OpenClaw documents a versioned security manifest schema.",
|
|
415
|
+
[
|
|
416
|
+
"Delete openclaw.security.json if it is advisory-only and not consumed by OpenClaw.",
|
|
417
|
+
"Reintroduce it only when the schema and ClawHub behavior are documented.",
|
|
418
|
+
],
|
|
419
|
+
),
|
|
258
420
|
},
|
|
259
421
|
};
|
|
260
422
|
|
|
@@ -284,6 +446,14 @@ export function buildIssues({ breakages = [], warnings = [], suggestions = [], t
|
|
|
284
446
|
evidence: finding.evidence ?? [],
|
|
285
447
|
compatRecord: finding.compatRecord ?? null,
|
|
286
448
|
runtimeCoverage: finding.runtimeCoverage ?? null,
|
|
449
|
+
...(finding.authorRemediation
|
|
450
|
+
? {
|
|
451
|
+
authorRemediation: {
|
|
452
|
+
summary: finding.authorRemediation.summary,
|
|
453
|
+
docsUrl: authorRemediationDocsUrl(finding.code),
|
|
454
|
+
},
|
|
455
|
+
}
|
|
456
|
+
: {}),
|
|
287
457
|
}));
|
|
288
458
|
}
|
|
289
459
|
|
|
@@ -305,9 +475,18 @@ export function issueMetadata(finding, targetOpenClaw) {
|
|
|
305
475
|
decision: "inspector-follow-up",
|
|
306
476
|
title: finding.message,
|
|
307
477
|
};
|
|
478
|
+
const authorMetadata = metadata.authorRemediation
|
|
479
|
+
? {
|
|
480
|
+
authorRemediation: {
|
|
481
|
+
summary: metadata.authorRemediation.summary,
|
|
482
|
+
docsUrl: authorRemediationDocsUrl(finding.code),
|
|
483
|
+
},
|
|
484
|
+
}
|
|
485
|
+
: {};
|
|
308
486
|
return {
|
|
309
487
|
...finding,
|
|
310
488
|
...metadata,
|
|
489
|
+
...authorMetadata,
|
|
311
490
|
...classifyIssueFinding(finding, targetOpenClaw, metadata),
|
|
312
491
|
};
|
|
313
492
|
}
|
|
@@ -337,6 +516,10 @@ export function isInspectorGapFinding(finding, targetOpenClaw) {
|
|
|
337
516
|
return issueMetadata(finding, targetOpenClaw).issueClass === "inspector-gap";
|
|
338
517
|
}
|
|
339
518
|
|
|
519
|
+
export function isAuthorFacingFinding(finding, targetOpenClaw) {
|
|
520
|
+
return Boolean(issueMetadata(finding, targetOpenClaw).authorRemediation);
|
|
521
|
+
}
|
|
522
|
+
|
|
340
523
|
export function summarizeIssueClasses(issues) {
|
|
341
524
|
const summary = {
|
|
342
525
|
"compat-gap": 0,
|
package/src/report.js
CHANGED
|
@@ -3,7 +3,7 @@ import { renderMarkdownTable, writeArtifacts, writeJsonMarkdownArtifacts } from
|
|
|
3
3
|
import { renderCompatibilityIssuesReport, renderCompatibilityMarkdownReport } from "./compatibility-report.js";
|
|
4
4
|
import { buildContractProbes } from "./contract-probes.js";
|
|
5
5
|
import { classifyCompatibilityFixture } from "./fixture-summary.js";
|
|
6
|
-
import { buildIssues,
|
|
6
|
+
import { buildIssues, isAuthorFacingFinding, summarizeIssueClasses } from "./issues.js";
|
|
7
7
|
import { sanitizeReportArtifact } from "./report-sanitizer.js";
|
|
8
8
|
import { applyRuntimeExecutionCoverage } from "./runtime-reconciliation.js";
|
|
9
9
|
|
|
@@ -141,14 +141,16 @@ export async function buildCompatibilityReport(options = {}) {
|
|
|
141
141
|
decisions,
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
+
const visibleBreakages = filterVisibleFindings(breakages, targetOpenClaw, options);
|
|
144
145
|
const visibleWarnings = filterVisibleFindings(warnings, targetOpenClaw, options);
|
|
145
146
|
const visibleSuggestions = filterVisibleFindings(suggestions, targetOpenClaw, options);
|
|
147
|
+
const visibleDecisions = options.authorFacing === true ? [] : decisions;
|
|
146
148
|
const runtimeCoverage = applyRuntimeExecutionCoverage({
|
|
147
149
|
findings: [...visibleWarnings, ...visibleSuggestions],
|
|
148
150
|
executionResults: options.executionResults,
|
|
149
151
|
});
|
|
150
152
|
const issues = buildIssues({
|
|
151
|
-
breakages,
|
|
153
|
+
breakages: visibleBreakages,
|
|
152
154
|
warnings: visibleWarnings,
|
|
153
155
|
suggestions: visibleSuggestions,
|
|
154
156
|
targetOpenClaw,
|
|
@@ -166,14 +168,14 @@ export async function buildCompatibilityReport(options = {}) {
|
|
|
166
168
|
return {
|
|
167
169
|
generatedAt: options.generatedAt ?? "deterministic",
|
|
168
170
|
targetOpenClaw,
|
|
169
|
-
status:
|
|
171
|
+
status: visibleBreakages.length === 0 ? "pass" : "fail",
|
|
170
172
|
summary: {
|
|
171
173
|
fixtureCount: fixtureReports.length,
|
|
172
174
|
highPriorityFixtures: fixtureReports.filter((fixture) => fixture.priority === "high").length,
|
|
173
|
-
breakageCount:
|
|
175
|
+
breakageCount: visibleBreakages.length,
|
|
174
176
|
warningCount: visibleWarnings.length,
|
|
175
177
|
suggestionCount: visibleSuggestions.length,
|
|
176
|
-
decisionCount:
|
|
178
|
+
decisionCount: visibleDecisions.length,
|
|
177
179
|
logCount: logs.length,
|
|
178
180
|
issueCount: issues.length,
|
|
179
181
|
openIssueCount: openIssues.length,
|
|
@@ -195,21 +197,21 @@ export async function buildCompatibilityReport(options = {}) {
|
|
|
195
197
|
contractProbeCount: contractProbes.length,
|
|
196
198
|
},
|
|
197
199
|
fixtures: fixtureReports,
|
|
198
|
-
breakages,
|
|
200
|
+
breakages: visibleBreakages,
|
|
199
201
|
warnings: visibleWarnings,
|
|
200
202
|
suggestions: visibleSuggestions,
|
|
201
203
|
issues,
|
|
202
204
|
contractProbes,
|
|
203
205
|
logs,
|
|
204
|
-
decisions,
|
|
206
|
+
decisions: visibleDecisions,
|
|
205
207
|
};
|
|
206
208
|
}
|
|
207
209
|
|
|
208
210
|
function filterVisibleFindings(findings, targetOpenClaw, options) {
|
|
209
|
-
if (options.
|
|
211
|
+
if (options.authorFacing !== true) {
|
|
210
212
|
return findings;
|
|
211
213
|
}
|
|
212
|
-
return findings.filter((finding) =>
|
|
214
|
+
return findings.filter((finding) => isAuthorFacingFinding(finding, targetOpenClaw));
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
export function classifyCompatRecordCoverage({ targetOpenClaw, findings, suggestions, logs, decisions }) {
|