@openclaw/plugin-inspector 0.3.5 → 0.3.6

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 CHANGED
@@ -2,7 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
- _No unreleased changes._
5
+ ## 0.3.6 - 2026-05-03
6
+
7
+ ### Changed
8
+
9
+ - Report import-loop RSS and CPU as baseline-adjusted plugin deltas alongside raw subprocess metrics so Crabpot dashboards do not treat harness import cost as plugin runtime cost.
10
+ - Include optional OpenClaw loader lifecycle timings for import and full activation when a capture runner provides them.
11
+
12
+ ### Fixed
13
+
14
+ - Accept plugin install minimum-host floors as supported package metadata.
15
+ - Flag unsupported legacy OpenClaw bundle metadata and advertised npm pack blockers.
16
+ - Reconcile runtime capture evidence and harden mocked capture paths for downstream fixture reports.
6
17
 
7
18
  ## 0.3.5 - 2026-04-29
8
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/plugin-inspector",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "private": false,
5
5
  "description": "Offline compatibility inspector for OpenClaw plugins.",
6
6
  "type": "module",
package/src/advanced.js CHANGED
@@ -95,6 +95,7 @@ export {
95
95
  classifyTargetOpenClawCoverage,
96
96
  readPackageSummaries,
97
97
  readPluginManifests,
98
+ readSecurityManifests,
98
99
  summarizePackage,
99
100
  } from "./fixture-summary.js";
100
101
  export {
@@ -183,6 +184,10 @@ export {
183
184
  validateRuntimeProfile,
184
185
  writeRuntimeProfile,
185
186
  } from "./runtime-profile.js";
187
+ export {
188
+ applyRuntimeExecutionCoverage,
189
+ buildRuntimeExecutionCoverage,
190
+ } from "./runtime-reconciliation.js";
186
191
  export {
187
192
  buildRuntimeCaptureReport,
188
193
  renderRuntimeCaptureMarkdown,
package/src/api.js CHANGED
@@ -45,6 +45,7 @@ export async function inspectPluginRoot(options = {}) {
45
45
  return inspectCompatibilityFixtureSet(config, {
46
46
  generatedAt: options.generatedAt,
47
47
  openclawPath: options.openclawPath,
48
+ executionResults: options.executionResults,
48
49
  targetOpenClaw: options.targetOpenClaw,
49
50
  });
50
51
  }
@@ -59,6 +60,7 @@ export async function inspectCompatibilityFixtureSetConfig(options = {}) {
59
60
  return inspectCompatibilityFixtureSet(config, {
60
61
  generatedAt: options.generatedAt,
61
62
  openclawPath: options.openclawPath,
63
+ executionResults: options.executionResults,
62
64
  targetOpenClaw: options.targetOpenClaw,
63
65
  });
64
66
  }
@@ -112,6 +114,7 @@ export async function buildFixtureSetColdImportReadiness(options = {}) {
112
114
  (await inspectCompatibilityFixtureSet(config, {
113
115
  generatedAt: options.generatedAt,
114
116
  openclawPath: options.openclawPath,
117
+ executionResults: options.executionResults,
115
118
  targetOpenClaw: options.targetOpenClaw,
116
119
  }));
117
120
 
@@ -143,6 +146,7 @@ export async function buildFixtureSetWorkspacePlan(options = {}) {
143
146
  (await inspectCompatibilityFixtureSet(config, {
144
147
  generatedAt: options.generatedAt,
145
148
  openclawPath: options.openclawPath,
149
+ executionResults: options.executionResults,
146
150
  targetOpenClaw: options.targetOpenClaw,
147
151
  }));
148
152
  const rootDir = options.rootDir ?? config?.rootDir ?? options.cwd;
package/src/artifacts.js CHANGED
@@ -95,7 +95,7 @@ export function renderPaddedMarkdownTable(rows, headers, options = {}) {
95
95
  }
96
96
 
97
97
  export function escapeMarkdownTableCell(value) {
98
- return value.replace(/\|/g, "\\|").replace(/\n/g, "<br>");
98
+ return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n/g, "<br>");
99
99
  }
100
100
 
101
101
  async function assertFileMatches(filePath, expected) {
package/src/ci-policy.js CHANGED
@@ -241,7 +241,9 @@ function executionChecks(executionResults, policy, options) {
241
241
  }
242
242
 
243
243
  function findPolicyMatch(rules, item) {
244
- return rules.find((rule) => item.seam === rule.seam && item.reason?.includes(rule.reasonIncludes));
244
+ return rules.find(
245
+ (rule) => (rule.seam === "*" || item.seam === rule.seam) && item.reason?.includes(rule.reasonIncludes),
246
+ );
245
247
  }
246
248
 
247
249
  function failedExecutionEvidence(executionResults) {
package/src/ci-summary.js CHANGED
@@ -56,8 +56,12 @@ export async function buildCiSummary(options = {}) {
56
56
  loaderJitiCandidates: reports.platform?.summary?.jitiAlternativeCount ?? 0,
57
57
  importLoopP50Ms: reports.importLoop?.summary?.p50WallMs ?? 0,
58
58
  importLoopP95Ms: reports.importLoop?.summary?.p95WallMs ?? 0,
59
- importLoopMaxRssMb: reports.importLoop?.summary?.maxPeakRssMb ?? 0,
60
- importLoopMaxCpuMs: reports.importLoop?.summary?.maxCpuMsEstimate ?? 0,
59
+ importLoopOpenClawLifecycleCount: reports.importLoop?.summary?.openClawLifecycleCount ?? 0,
60
+ importLoopOpenClawImportP50Ms: reports.importLoop?.summary?.p50OpenClawImportMs ?? 0,
61
+ importLoopOpenClawActivationP50Ms: reports.importLoop?.summary?.p50OpenClawActivationMs ?? 0,
62
+ importLoopMetricBasis: reports.importLoop?.summary?.maxPluginPeakRssDeltaMb === undefined ? "raw" : "baseline-adjusted",
63
+ importLoopMaxRssMb: reports.importLoop?.summary?.maxPluginPeakRssDeltaMb ?? reports.importLoop?.summary?.maxPeakRssMb ?? 0,
64
+ importLoopMaxCpuMs: reports.importLoop?.summary?.maxPluginCpuDeltaMsEstimate ?? reports.importLoop?.summary?.maxCpuMsEstimate ?? 0,
61
65
  importLoopRssSampleCount: metricSampleCount(reports.importLoop, "rss", "maxPeakRssMb"),
62
66
  importLoopCpuSampleCount: metricSampleCount(reports.importLoop, "cpu", "maxCpuMsEstimate"),
63
67
  },
@@ -150,7 +154,7 @@ export function renderCiSummaryMarkdown(summary) {
150
154
  ["Jiti loader candidates", summary.summary.loaderJitiCandidates],
151
155
  [
152
156
  "Import loop",
153
- `p50 ${summary.summary.importLoopP50Ms} ms / p95 ${summary.summary.importLoopP95Ms} ms / max RSS ${formatSampledMetric(summary.summary.importLoopMaxRssMb, summary.summary.importLoopRssSampleCount)} / CPU ${formatSampledMetric(summary.summary.importLoopMaxCpuMs, summary.summary.importLoopCpuSampleCount, "ms")}`,
157
+ importLoopSummaryLabel(summary.summary),
154
158
  ],
155
159
  ],
156
160
  ["Metric", "Value"],
@@ -249,6 +253,15 @@ function inferSampleCount(samples = [], kind) {
249
253
  }, 0);
250
254
  }
251
255
 
256
+ function importLoopSummaryLabel(summary) {
257
+ const metricLabel = summary.importLoopMetricBasis === "baseline-adjusted" ? "plugin delta" : "raw";
258
+ const lifecycle =
259
+ summary.importLoopOpenClawLifecycleCount > 0
260
+ ? ` / OpenClaw import ${summary.importLoopOpenClawImportP50Ms} ms / activate ${summary.importLoopOpenClawActivationP50Ms} ms`
261
+ : "";
262
+ return `p50 ${summary.importLoopP50Ms} ms / p95 ${summary.importLoopP95Ms} ms / ${metricLabel} RSS ${formatSampledMetric(summary.importLoopMaxRssMb, summary.importLoopRssSampleCount)} / ${metricLabel} CPU ${formatSampledMetric(summary.importLoopMaxCpuMs, summary.importLoopCpuSampleCount, "ms")}${lifecycle}`;
263
+ }
264
+
252
265
  function formatSampledMetric(value, count, unit = "MB") {
253
266
  if ((count ?? 0) <= 0) {
254
267
  return "n/a";
@@ -26,13 +26,20 @@ export function renderCompatibilityMarkdownReport(report, options = {}) {
26
26
  ["Warnings", report.summary.warningCount],
27
27
  ["Compatibility suggestions", report.summary.suggestionCount],
28
28
  ["Issue findings", report.summary.issueCount],
29
+ ["Open issue findings", report.summary.openIssueCount ?? report.summary.issueCount],
30
+ ["Runtime-covered findings", report.summary.runtimeCoveredIssueCount ?? 0],
31
+ ["Runtime-partial findings", report.summary.runtimePartiallyCoveredIssueCount ?? 0],
29
32
  ["P0 issues", report.summary.p0IssueCount],
30
33
  ["P1 issues", report.summary.p1IssueCount],
34
+ ["Open P0 issues", report.summary.openP0IssueCount ?? report.summary.p0IssueCount],
35
+ ["Open P1 issues", report.summary.openP1IssueCount ?? report.summary.p1IssueCount],
31
36
  ["Live issues", report.summary.liveIssueCount],
32
37
  ["Live P0 issues", report.summary.liveP0IssueCount],
33
38
  ["Compat gaps", report.summary.compatGapCount],
34
39
  ["Deprecation warnings", report.summary.deprecationWarningCount],
35
40
  ["Inspector gaps", report.summary.inspectorGapCount],
41
+ ["Open inspector gaps", report.summary.openInspectorGapCount ?? report.summary.inspectorGapCount],
42
+ ["Runtime coverage artifacts", report.summary.runtimeCoverageArtifactCount ?? 0],
36
43
  ["Upstream metadata", report.summary.upstreamIssueCount],
37
44
  ["Contract probes", report.summary.contractProbeCount],
38
45
  ["Decision rows", report.summary.decisionCount],
@@ -65,7 +72,11 @@ export function renderCompatibilityMarkdownReport(report, options = {}) {
65
72
  "",
66
73
  "## Inspector Proof Gaps",
67
74
  "",
68
- issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap"), options),
75
+ issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap" && issue.status !== "runtime-covered"), options),
76
+ "",
77
+ "## Runtime-Covered Inspector Gaps",
78
+ "",
79
+ issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap" && issue.status === "runtime-covered"), options),
69
80
  "",
70
81
  "## Upstream Metadata Issues",
71
82
  "",
@@ -141,13 +152,20 @@ export function renderCompatibilityIssuesReport(report, options = {}) {
141
152
  markdownTable(
142
153
  [
143
154
  ["Issue findings", report.summary.issueCount],
155
+ ["Open issue findings", report.summary.openIssueCount ?? report.summary.issueCount],
156
+ ["Runtime-covered findings", report.summary.runtimeCoveredIssueCount ?? 0],
157
+ ["Runtime-partial findings", report.summary.runtimePartiallyCoveredIssueCount ?? 0],
144
158
  [severityLabel("P0", options), report.summary.p0IssueCount],
145
159
  [severityLabel("P1", options), report.summary.p1IssueCount],
160
+ [`Open ${severityLabel("P0", options)}`, report.summary.openP0IssueCount ?? report.summary.p0IssueCount],
161
+ [`Open ${severityLabel("P1", options)}`, report.summary.openP1IssueCount ?? report.summary.p1IssueCount],
146
162
  ["Live issues", report.summary.liveIssueCount],
147
163
  ["Live P0 issues", report.summary.liveP0IssueCount],
148
164
  ["Compat gaps", report.summary.compatGapCount],
149
165
  ["Deprecation warnings", report.summary.deprecationWarningCount],
150
166
  ["Inspector gaps", report.summary.inspectorGapCount],
167
+ ["Open inspector gaps", report.summary.openInspectorGapCount ?? report.summary.inspectorGapCount],
168
+ ["Runtime coverage artifacts", report.summary.runtimeCoverageArtifactCount ?? 0],
151
169
  ["Upstream metadata", report.summary.upstreamIssueCount],
152
170
  ["Contract probes", report.summary.contractProbeCount],
153
171
  ],
@@ -179,7 +197,11 @@ export function renderCompatibilityIssuesReport(report, options = {}) {
179
197
  "",
180
198
  "## Inspector Proof Gaps",
181
199
  "",
182
- issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap"), options),
200
+ issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap" && issue.status !== "runtime-covered"), options),
201
+ "",
202
+ "## Runtime-Covered Inspector Gaps",
203
+ "",
204
+ issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap" && issue.status === "runtime-covered"), options),
183
205
  "",
184
206
  "## Upstream Metadata Issues",
185
207
  "",
@@ -228,6 +250,7 @@ function issueBlock(issue, options) {
228
250
  ` - state: ${issueState(issue)}`,
229
251
  " - evidence:",
230
252
  ...evidenceList(issue.evidence, options).map((item) => ` - ${item}`),
253
+ ...runtimeCoverageList(issue, options),
231
254
  ].join("\n");
232
255
  }
233
256
 
@@ -235,6 +258,7 @@ function issueState(issue) {
235
258
  const flags = [
236
259
  issue.status,
237
260
  `compat:${issue.compatStatus ?? "none"}`,
261
+ issue.runtimeCoverage?.status ? `runtime:${issue.runtimeCoverage.status}` : null,
238
262
  issue.live ? "live" : null,
239
263
  issue.deprecated ? "deprecated" : null,
240
264
  ].filter(Boolean);
@@ -266,7 +290,7 @@ function triageOverview(report) {
266
290
  "inspector-gap",
267
291
  report.summary.inspectorGapCount,
268
292
  "-",
269
- "Plugin Inspector needs stronger capture/probe evidence before making contract judgments.",
293
+ "Plugin Inspector needs stronger capture/probe evidence before making contract judgments. Runtime-covered rows are proof-backed and not open report work.",
270
294
  ],
271
295
  [
272
296
  "upstream-metadata",
@@ -357,3 +381,15 @@ function evidenceList(evidence, options) {
357
381
  const formatEvidence = options.formatEvidence ?? ((item) => item);
358
382
  return items.map((item) => formatEvidence(item));
359
383
  }
384
+
385
+ function runtimeCoverageList(issue, options) {
386
+ const runtimeCoverage = issue.runtimeCoverage;
387
+ if (!runtimeCoverage) {
388
+ return [];
389
+ }
390
+ return [
391
+ " - runtime coverage:",
392
+ ...evidenceList(runtimeCoverage.captured, options).map((item) => ` - captured ${item}`),
393
+ ...evidenceList(runtimeCoverage.artifacts, options).map((item) => ` - ${item}`),
394
+ ];
395
+ }
package/src/config.js CHANGED
@@ -202,13 +202,43 @@ export function packageId(packageName) {
202
202
  if (!packageName) {
203
203
  return null;
204
204
  }
205
- return packageName
205
+ const packageBase = packageName
206
206
  .split("/")
207
207
  .pop()
208
- .replace(/^openclaw-/, "")
209
- .replace(/[^a-zA-Z0-9]+/g, "-")
210
- .replace(/^-+|-+$/g, "")
211
- .toLowerCase();
208
+ .replace(/^openclaw-/, "");
209
+ return trimHyphenEdges(collapsePackageIdSeparators(packageBase)).toLowerCase();
210
+ }
211
+
212
+ function collapsePackageIdSeparators(value) {
213
+ let result = "";
214
+ let previousWasHyphen = false;
215
+ for (const char of value) {
216
+ if (isAsciiAlphaNumeric(char)) {
217
+ result += char;
218
+ previousWasHyphen = false;
219
+ } else if (!previousWasHyphen) {
220
+ result += "-";
221
+ previousWasHyphen = true;
222
+ }
223
+ }
224
+ return result;
225
+ }
226
+
227
+ function isAsciiAlphaNumeric(char) {
228
+ const code = char.charCodeAt(0);
229
+ return (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
230
+ }
231
+
232
+ function trimHyphenEdges(value) {
233
+ let start = 0;
234
+ let end = value.length;
235
+ while (start < end && value[start] === "-") {
236
+ start += 1;
237
+ }
238
+ while (end > start && value[end - 1] === "-") {
239
+ end -= 1;
240
+ }
241
+ return value.slice(start, end);
212
242
  }
213
243
 
214
244
  export function inferPluginSeams(pluginManifest, packageJson) {
@@ -74,6 +74,31 @@ export const contractProbeRules = {
74
74
  contract: "OpenClaw package entrypoints resolve to files in the published or built plugin package.",
75
75
  target: "package-loader",
76
76
  },
77
+ "package-install-metadata-incomplete": {
78
+ id: "package.metadata.install-release",
79
+ contract: "Release publishing metadata declares canonical ClawHub and npm install specs.",
80
+ target: "package-loader",
81
+ },
82
+ "package-min-host-version-drift": {
83
+ id: "package.metadata.min-host-version",
84
+ contract: "Install minimum host version matches the OpenClaw package surface targeted by the plugin.",
85
+ target: "package-loader",
86
+ },
87
+ "package-npm-pack-entrypoint-missing": {
88
+ id: "package.npm-pack.entrypoints",
89
+ contract: "Advertised npm artifacts include every declared OpenClaw package entrypoint.",
90
+ target: "package-loader",
91
+ },
92
+ "package-npm-pack-metadata-missing": {
93
+ id: "package.npm-pack.metadata",
94
+ contract: "Advertised npm artifacts include OpenClaw manifest and package metadata.",
95
+ target: "package-loader",
96
+ },
97
+ "package-npm-pack-unavailable": {
98
+ id: "package.npm-pack.available",
99
+ contract: "Packages that advertise npm install support can produce an npm pack artifact.",
100
+ target: "package-loader",
101
+ },
77
102
  "package-openclaw-entry-missing": {
78
103
  id: "package.entrypoint.openclaw-metadata",
79
104
  contract: "OpenClaw package metadata declares entrypoints for cold import and registration capture.",