@openclaw/plugin-inspector 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.3.2 - 2026-04-28
6
+
7
+ ### Fixed
8
+
9
+ - Preserve runtime capture bindings for callback-based registrations so captured hooks and registrations can resolve their bound callback metadata.
10
+
11
+ ## 0.3.1 - 2026-04-28
12
+
13
+ ### Added
14
+
15
+ - Add grouped root facades: `pluginRoot`, `fixtureSuites`, `staticInspection`, `reports`, `contracts`, `ci`, `runtime`, and `synthetic`.
16
+ - Expose contract capture, contract coverage, CI rollup, runtime profile, ref/profile diff, import-loop, and synthetic probe helpers from the root package API.
17
+ - Add a release follow-through guard that fails when Crabpot scripts regress to the legacy `advanced.js` bundle.
18
+ - Add a package-contents release guard for npm tarball entrypoints, examples, and README assets.
19
+
20
+ ### Changed
21
+
22
+ - Move Crabpot integration scripts to the root public API while keeping Crabpot as the fixture corpus and report consumer.
23
+ - Keep generic artifact writing out of the root API; Crabpot-owned runner scripts write their own JSON outputs.
24
+ - Ship the README banner asset in the npm package so the published README does not reference a missing local image.
25
+ - Document the grouped root import path for embedding harnesses without turning the README into a full API dump.
26
+
3
27
  ## 0.3.0 - 2026-04-27
4
28
 
5
29
  ### Added
package/README.md CHANGED
@@ -1,90 +1,131 @@
1
- <img src="docs/plugin-inspector-banner.jpg" alt="openclaw plugin inspector banner"/>
1
+ <img src="docs/plugin-inspector-banner.jpg" alt="OpenClaw Plugin Inspector banner">
2
2
 
3
3
  # OpenClaw Plugin Inspector
4
4
 
5
- `plugin-inspector` is the offline compatibility check for OpenClaw plugins. Run
6
- it from a plugin root to inspect package metadata, `openclaw.plugin.json`, SDK
7
- imports, `api.on(...)`, `api.register*`, and optional runtime registration
8
- capture.
5
+ `@openclaw/plugin-inspector` is the offline compatibility checker for OpenClaw
6
+ plugin packages and plugin fixture suites.
9
7
 
10
- ## Quick Start
8
+ It answers the questions that matter before a plugin reaches users:
11
9
 
12
- From a plugin package directory:
10
+ - can OpenClaw discover the package metadata and `openclaw.plugin.json`
11
+ manifest?
12
+ - which hooks, registration calls, manifest contracts, and SDK imports does the
13
+ plugin use?
14
+ - does the plugin still look compatible without local OpenClaw internals?
15
+ - if CI finds a breakage, which JSON, Markdown, SARIF, JUnit, and summary
16
+ artifacts should downstream automation read?
17
+ - when a fixture-suite harness such as Crabpot runs many plugins, which findings
18
+ are hard breakages, known warnings, live issues, deprecations, or inspector
19
+ proof gaps?
13
20
 
14
- ```bash
15
- npx @openclaw/plugin-inspector
16
- ```
21
+ The default path is static, offline, and credential-free. Runtime capture exists,
22
+ but it is opt-in because it imports plugin code.
17
23
 
18
- That runs `check`, writes report artifacts to `reports/`, and exits non-zero
19
- when compatibility breakages are found.
24
+ ## Requirements
20
25
 
21
- Add a local config and GitHub Actions workflow:
26
+ - Node.js 22 or newer.
27
+ - A plugin package root with `package.json`.
28
+ - `openclaw.plugin.json` when the plugin uses the OpenClaw manifest contract.
29
+ - No OpenClaw checkout, credentials, network service, or live provider access for
30
+ default inspection.
22
31
 
23
- ```bash
24
- npx @openclaw/plugin-inspector init --ci
25
- ```
32
+ Pass `--no-openclaw` when CI should not compare against a local OpenClaw
33
+ checkout. If an OpenClaw checkout is supplied with `--openclaw <path>`, the
34
+ inspector only reads public compatibility surfaces such as compat records, SDK
35
+ exports, hook names, manifest fields, and registrar metadata.
26
36
 
27
- `init --ci` detects `packageManager` and common lockfiles. Pass
28
- `--package-manager pnpm`, `npm`, `yarn`, or `bun` when you want to override it.
29
- Add `--scripts` to write `plugin:check` and `plugin:ci` package scripts.
30
- Use `--dry-run` to preview the files first.
37
+ ## Quick Start
31
38
 
32
- Or install it as a dev dependency:
39
+ Run this from a plugin package root:
33
40
 
34
41
  ```bash
35
- npm install --save-dev @openclaw/plugin-inspector
36
- npx plugin-inspector check
42
+ npx @openclaw/plugin-inspector inspect --no-openclaw
37
43
  ```
38
44
 
39
- ## Commands
45
+ Equivalent one-off runners:
40
46
 
41
47
  ```bash
42
- npx @openclaw/plugin-inspector check
43
- npx @openclaw/plugin-inspector inspect
44
- npx @openclaw/plugin-inspector ci --no-openclaw
45
- npx @openclaw/plugin-inspector config
46
- npx @openclaw/plugin-inspector check --plugin-root ./plugins/weather
47
- npx @openclaw/plugin-inspector init --ci --package-manager pnpm
48
- npx @openclaw/plugin-inspector init --ci --scripts --dry-run
49
- npx @openclaw/plugin-inspector init --ci --scripts --dry-run --json
48
+ pnpm dlx @openclaw/plugin-inspector inspect --no-openclaw
49
+ yarn dlx @openclaw/plugin-inspector inspect --no-openclaw
50
+ bunx @openclaw/plugin-inspector inspect --no-openclaw
50
51
  ```
51
52
 
52
- `check` and `inspect` read the current directory as one plugin unless
53
- `--plugin-root` is set. `inspect` is the friendly author-facing alias; `check`
54
- is kept for scripts. Both write:
53
+ The command writes:
55
54
 
56
55
  - `reports/plugin-inspector-report.json`
57
56
  - `reports/plugin-inspector-report.md`
58
57
  - `reports/plugin-inspector-issues.md`
59
58
 
60
- Use CI-native outputs when you want annotations or test-summary ingestion:
59
+ It exits non-zero when hard compatibility breakages are found. Warnings,
60
+ suggestions, issue classifications, and logs stay visible in the report without
61
+ necessarily failing the command.
62
+
63
+ ## Install In A Plugin Repo
64
+
65
+ Install the package when you want repeatable local scripts and CI:
61
66
 
62
67
  ```bash
63
- plugin-inspector inspect --no-openclaw --sarif --junit
68
+ npm install --save-dev @openclaw/plugin-inspector
64
69
  ```
65
70
 
66
- That also writes:
71
+ Add scripts:
67
72
 
68
- - `reports/plugin-inspector.sarif`
69
- - `reports/plugin-inspector.junit.xml`
73
+ ```json
74
+ {
75
+ "scripts": {
76
+ "plugin:check": "plugin-inspector inspect --no-openclaw",
77
+ "plugin:ci": "plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute"
78
+ }
79
+ }
80
+ ```
70
81
 
71
- `config` prints the resolved plugin root, fixture id, seams, and capture
72
- settings before CI runs:
82
+ Then run:
73
83
 
74
84
  ```bash
75
- plugin-inspector config --json
85
+ npm run plugin:check
76
86
  ```
77
87
 
78
- Use `--no-openclaw` when CI should not compare against a local OpenClaw
79
- checkout:
88
+ The initializer can write the starter config, package scripts, and GitHub
89
+ Actions workflow:
80
90
 
81
91
  ```bash
82
- plugin-inspector check --no-openclaw
92
+ npx @openclaw/plugin-inspector init --ci --scripts --dry-run
93
+ npx @openclaw/plugin-inspector init --ci --scripts
94
+ ```
95
+
96
+ `init` detects `packageManager` and common lockfiles. Override that with
97
+ `--package-manager npm`, `--package-manager pnpm`, `--package-manager yarn`, or
98
+ `--package-manager bun`. Existing files are protected unless you pass `--force`.
99
+
100
+ ## Configuration
101
+
102
+ Small plugin repos can keep configuration in `package.json`:
103
+
104
+ ```json
105
+ {
106
+ "scripts": {
107
+ "plugin:check": "plugin-inspector inspect --no-openclaw",
108
+ "plugin:ci": "plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute"
109
+ },
110
+ "pluginInspector": {
111
+ "version": 1,
112
+ "plugin": {
113
+ "id": "weather",
114
+ "priority": "high",
115
+ "seams": ["dynamic-tool"],
116
+ "sourceRoot": "src",
117
+ "expect": {
118
+ "registrations": ["registerTool"]
119
+ }
120
+ },
121
+ "capture": {
122
+ "mockSdk": true
123
+ }
124
+ }
125
+ }
83
126
  ```
84
127
 
85
- Use either `plugin-inspector.config.json` or a `package.json`
86
- `pluginInspector` block when CI needs stable fixture metadata, expected seams,
87
- or runtime capture defaults:
128
+ Use `plugin-inspector.config.json` for a standalone config file:
88
129
 
89
130
  ```json
90
131
  {
@@ -107,66 +148,88 @@ or runtime capture defaults:
107
148
  }
108
149
  ```
109
150
 
110
- Then run:
151
+ Inspect the resolved config before wiring CI:
111
152
 
112
153
  ```bash
113
- plugin-inspector check --config plugin-inspector.config.json
154
+ plugin-inspector config --json
114
155
  ```
115
156
 
116
- For a single plugin package, the same config can live in `package.json`:
157
+ Copy-ready examples live in:
117
158
 
118
- ```json
119
- {
120
- "scripts": {
121
- "plugin:check": "plugin-inspector inspect --no-openclaw"
122
- },
123
- "pluginInspector": {
124
- "version": 1,
125
- "plugin": {
126
- "id": "weather",
127
- "priority": "high",
128
- "seams": ["dynamic-tool"],
129
- "sourceRoot": "src"
130
- },
131
- "capture": {
132
- "mockSdk": true
133
- }
134
- }
135
- }
136
- ```
159
+ - `examples/plugin-inspector.config.json`
160
+ - `examples/package-json-plugin-inspector.json`
161
+
162
+ ## Commands
137
163
 
138
- `init --ci` writes this shape for you, plus
139
- `.github/workflows/plugin-inspector.yml`. Copy-ready examples also live in
140
- `examples/plugin-inspector.config.json` and
141
- `examples/package-json-plugin-inspector.json` and
142
- `examples/github-actions-plugin-inspector.yml`. SARIF/JUnit CI consumption
143
- examples live alongside them.
164
+ | Command | Purpose |
165
+ | --- | --- |
166
+ | `plugin-inspector` | Default alias for `check`. |
167
+ | `plugin-inspector check` | Script-friendly plugin-root check. |
168
+ | `plugin-inspector inspect` | Plugin-root check unless `--config` is supplied; with `--config`, runs a fixture report. |
169
+ | `plugin-inspector ci` | Compatibility report plus CI summary, SARIF, and JUnit outputs. |
170
+ | `plugin-inspector config` | Print resolved plugin-root config as text or JSON. |
171
+ | `plugin-inspector init` | Write starter config, scripts, and optional GitHub Actions workflow. |
172
+ | `plugin-inspector report` | Run a fixture-suite config with many plugins. |
173
+ | `plugin-inspector capture` | Runtime-capture one entrypoint directly. |
174
+
175
+ Common options:
176
+
177
+ | Option | Meaning |
178
+ | --- | --- |
179
+ | `--plugin-root <path>` / `--root <path>` | Check a plugin somewhere other than the current directory. |
180
+ | `--config <path>` | Read a standalone config file. Required for fixture-suite `report`. |
181
+ | `--out <dir>` | Write reports somewhere other than `reports/`. |
182
+ | `--openclaw <path>` | Compare against a local OpenClaw checkout. |
183
+ | `--no-openclaw` | Disable OpenClaw checkout comparison. |
184
+ | `--runtime` / `--capture` | Add opt-in runtime registration capture. |
185
+ | `--no-runtime` / `--no-capture` | Disable runtime capture even when config enables it. |
186
+ | `--mock-sdk` / `--sdk mock` | Use generated SDK and external-package mocks for runtime capture. |
187
+ | `--real-sdk` / `--sdk real` | Use installed real SDK dependencies instead of mocks. |
188
+ | `--allow-execute` | Permit commands that import plugin code. |
189
+ | `--json` | Print machine-readable JSON to stdout. |
190
+ | `--sarif [path]` | Write SARIF from `check` or `inspect`; `ci` enables this by default. |
191
+ | `--junit [path]` | Write JUnit XML from `check` or `inspect`; `ci` enables this by default. |
192
+ | `--no-sarif` / `--no-junit` | Disable default `ci` outputs. |
193
+
194
+ Run the built-in help for the exact CLI surface:
195
+
196
+ ```bash
197
+ plugin-inspector --help
198
+ ```
144
199
 
145
200
  ## Runtime Capture
146
201
 
147
202
  Runtime capture imports plugin entrypoints in an isolated subprocess and records
148
- the registrations made during `register(api)`. It is opt-in because it executes
149
- plugin code:
203
+ what `register(api)` does. Use it when static inspection cannot prove the actual
204
+ registrations made at runtime.
150
205
 
151
206
  ```bash
152
- npx @openclaw/plugin-inspector check --runtime --mock-sdk --allow-execute
207
+ plugin-inspector inspect --no-openclaw --runtime --mock-sdk --allow-execute
153
208
  ```
154
209
 
155
- `--allow-execute` is the explicit guard for modes that import plugin code. The
156
- older `PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1` environment guard still works for
157
- custom harnesses.
210
+ `--allow-execute` is the deliberate safety switch. Without it, modes that import
211
+ plugin code fail closed. The older environment guard still works for custom
212
+ harnesses:
158
213
 
159
- By default, runtime capture uses a generated mock for `openclaw/plugin-sdk` and
160
- common external packages so plugin code can load in clean CI without OpenClaw
161
- installed. Use `--real-sdk` only when the plugin workspace already has real SDK
162
- dependencies installed and you intentionally want to test that path.
214
+ ```bash
215
+ PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector inspect --no-openclaw --runtime --mock-sdk
216
+ ```
217
+
218
+ By default, runtime capture uses generated mocks for `openclaw/plugin-sdk`
219
+ subpaths and unresolved external packages discovered in the plugin import graph.
220
+ That keeps compatibility CI offline and credential-free. It does not call live
221
+ services, launch OpenClaw, run provider SDKs, or emulate service lifecycle side
222
+ effects.
223
+
224
+ Use `--real-sdk` only when the plugin workspace already has real SDK
225
+ dependencies installed and you intentionally want that path.
163
226
 
164
227
  Runtime capture writes:
165
228
 
166
229
  - `reports/plugin-inspector-runtime-capture.json`
167
230
  - `reports/plugin-inspector-runtime-capture.md`
168
231
 
169
- You can also capture one entrypoint directly:
232
+ Capture one entrypoint directly:
170
233
 
171
234
  ```bash
172
235
  plugin-inspector capture ./dist/index.js --mock-sdk --allow-execute
@@ -174,18 +237,10 @@ plugin-inspector capture ./dist/index.js --mock-sdk --allow-execute
174
237
 
175
238
  ## CI
176
239
 
177
- Minimal package scripts:
178
-
179
- ```json
180
- {
181
- "scripts": {
182
- "plugin:check": "plugin-inspector inspect --no-openclaw",
183
- "plugin:ci": "plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute"
184
- }
185
- }
186
- ```
240
+ `plugin-inspector ci` writes the normal compatibility report plus CI-native
241
+ summary, SARIF, and JUnit artifacts.
187
242
 
188
- GitHub Actions without a local dev dependency:
243
+ Minimal GitHub Actions workflow:
189
244
 
190
245
  ```yaml
191
246
  name: plugin-inspector
@@ -213,50 +268,214 @@ jobs:
213
268
  path: reports/plugin-inspector-*
214
269
  ```
215
270
 
216
- `ci` writes the normal report, CI summary, SARIF, and JUnit files by default.
217
- Pass `--no-sarif` or `--no-junit` only if your CI surface cannot consume them.
271
+ Generated `ci` artifacts:
272
+
273
+ - `reports/plugin-inspector-report.json`
274
+ - `reports/plugin-inspector-report.md`
275
+ - `reports/plugin-inspector-issues.md`
276
+ - `reports/plugin-inspector-ci-summary.json`
277
+ - `reports/plugin-inspector-ci-summary.md`
278
+ - `reports/plugin-inspector.sarif`
279
+ - `reports/plugin-inspector.junit.xml`
280
+
281
+ CI examples:
282
+
283
+ - `examples/github-actions-plugin-inspector.yml`
284
+ - `examples/github-actions-code-scanning.yml`
285
+ - `examples/gitlab-ci-plugin-inspector.yml`
286
+ - `examples/circleci-plugin-inspector.yml`
287
+
288
+ ## Report Surfaces
289
+
290
+ The compatibility report is the primary contract. Preserve field names and
291
+ finding codes because downstream CI and Crabpot reports may consume them.
292
+
293
+ Important report sections:
294
+
295
+ | Field | Meaning |
296
+ | --- | --- |
297
+ | `status` | `pass` unless hard breakages exist. |
298
+ | `summary` | Counts for fixtures, breakages, warnings, suggestions, issues, issue classes, and contract probes. |
299
+ | `targetOpenClaw` | Status and public compatibility data read from the optional OpenClaw checkout. |
300
+ | `fixtures` | Per-plugin metadata, hooks, registrations, manifest contracts, package data, and SDK imports. |
301
+ | `breakages` | Blocking compatibility failures. |
302
+ | `warnings` / `suggestions` | Non-blocking compatibility findings. |
303
+ | `issues` | Normalized issue rows with severity and class. |
304
+ | `contractProbes` | Suggested synthetic probes derived from observed contracts. |
305
+ | `logs` | Informational inventory and coverage rows. |
306
+ | `decisions` | Maintainer-facing follow-up or compatibility-policy decisions. |
307
+
308
+ Issue classes currently flow through the reports as live issues, compat gaps,
309
+ deprecation warnings, inspector gaps, upstream metadata, and fixture regressions.
310
+
311
+ ## CI Policy And Shared Reporting Primitives
312
+
313
+ `plugin-inspector` owns the shared CI policy and report rendering primitives.
314
+ Fixture-suite harnesses such as Crabpot should call these exports instead of
315
+ reimplementing scoring, summaries, Markdown, SARIF, or JUnit handling.
316
+
317
+ The root API exposes grouped helpers:
318
+
319
+ ```js
320
+ import { ci } from "@openclaw/plugin-inspector";
321
+
322
+ const policyReport = ci.buildPolicyReport({
323
+ policy,
324
+ compatibilityReport,
325
+ executionResults,
326
+ strict: false,
327
+ });
328
+
329
+ await ci.writePolicyReport(policyReport);
330
+ ```
331
+
332
+ CI policy reports default to:
333
+
334
+ - `reports/plugin-inspector-ci-policy.json`
335
+ - `reports/plugin-inspector-ci-policy.md`
218
336
 
219
- For GitHub code scanning, use
220
- `examples/github-actions-code-scanning.yml`; it uploads
221
- `reports/plugin-inspector.sarif` through CodeQL's SARIF upload action.
337
+ A policy must use `version: 1` and define:
222
338
 
223
- For CI test-summary UIs, point JUnit ingestion at
224
- `reports/plugin-inspector.junit.xml`. Copy-ready GitLab and CircleCI examples
225
- live in `examples/gitlab-ci-plugin-inspector.yml` and
226
- `examples/circleci-plugin-inspector.yml`.
339
+ - `allowedBlocked`
340
+ - `expectedWarnings`
341
+ - `thresholds`
342
+ - `fixtureSets`
343
+
344
+ Policy scoring fails hard breakages, unknown blocked synthetic probes, hard ref
345
+ diff regressions, failed execution results, strict live P0 issues, and strict
346
+ classified blockers. Non-strict mode keeps classified blocked probes and live P0
347
+ issues visible as warnings.
348
+
349
+ CI summary helpers read the known report set from `reports/` and render one
350
+ machine-readable and one Markdown rollup:
351
+
352
+ - compatibility
353
+ - runtime capture
354
+ - synthetic probes
355
+ - cold import readiness
356
+ - workspace plan
357
+ - platform probes
358
+ - import-loop profile
359
+ - execution results
360
+ - runtime profile
361
+ - ref diff
362
+ - profile diff
363
+ - CI policy
227
364
 
228
365
  ## Fixture Suites
229
366
 
230
- Fixture-set configs are still supported for crabpot-style compatibility suites:
367
+ Most plugin authors should use the plugin-root workflow. Use fixture suites when
368
+ one repository intentionally checks many plugins or packages, as Crabpot does.
231
369
 
232
370
  ```bash
233
371
  plugin-inspector report --config crabpot.config.json --out reports
372
+ plugin-inspector report --config crabpot.config.json --out reports --check
373
+ plugin-inspector ci --config crabpot.config.json --out reports --no-openclaw
374
+ ```
375
+
376
+ Fixture-suite configs are loaded through the explicit fixture helpers. That keeps
377
+ normal plugin-root configuration simple while still supporting bulk compatibility
378
+ harnesses.
379
+
380
+ ## Public API
381
+
382
+ Prefer the CLI for normal plugin repositories. Import the public API when a test
383
+ harness needs to compose workflows directly:
384
+
385
+ ```js
386
+ import { pluginRoot } from "@openclaw/plugin-inspector";
387
+
388
+ const { report, paths } = await pluginRoot.runCheck({
389
+ pluginRoot: process.cwd(),
390
+ openclawPath: false,
391
+ outDir: "reports",
392
+ });
393
+
394
+ console.log(report.status, paths.jsonPath);
395
+ ```
396
+
397
+ Stable grouped facades:
398
+
399
+ | Facade | Use |
400
+ | --- | --- |
401
+ | `pluginRoot` | Load config, inspect, run checks, capture entrypoints, or set up a plugin repo. |
402
+ | `fixtureSuites` | Load fixture-suite configs, run reports, and build fixture-suite readiness plans. |
403
+ | `staticInspection` | Inspect source text or fixture sets without the compatibility report layer. |
404
+ | `reports` | Render/write reports and classify issue findings. |
405
+ | `contracts` | Build, render, validate, and write contract captures and coverage. |
406
+ | `ci` | Build summaries, policy reports, execution results, SARIF, and JUnit outputs. |
407
+ | `runtime` | Build runtime profiles, profile diffs, ref diffs, and import-loop profiles. |
408
+ | `synthetic` | Build and run synthetic probe plans. |
409
+
410
+ Named exports remain available for existing automation. Prefer the grouped
411
+ facades for new code because they show ownership and keep downstream wrappers
412
+ thin.
413
+
414
+ ## Development
415
+
416
+ Repository checks are intentionally small and offline:
417
+
418
+ ```bash
419
+ npm test
420
+ npm run release:contents
421
+ npm run check
234
422
  ```
235
423
 
236
- Use fixture suites when one repo wants to inspect many plugins. Use plugin-root
237
- `check` for normal plugin CI.
424
+ `npm run check` runs the Node test suite and the package-contents guard. The
425
+ contents guard shells through `npm pack --dry-run --json` and verifies the npm
426
+ tarball includes package entrypoints, examples, README assets, and no private
427
+ `test/`, `scripts/`, or `.github/` paths.
428
+
429
+ Useful release-prep commands:
238
430
 
239
- ## Mocking Model
431
+ ```bash
432
+ npm run release:local
433
+ npm run release:readiness
434
+ npm run release:notes
435
+ npm run release:plan
436
+ npm run release:crabpot -- --crabpot ../crabpot
437
+ ```
240
438
 
241
- Default inspection is static, offline, and credential-free. Runtime capture is
242
- the only mode that imports plugin code.
439
+ `release:readiness` proves the local package and verifies Crabpot follow-through.
440
+ It does not publish.
243
441
 
244
- When `--mock-sdk` is enabled, the inspector generates temporary modules for
245
- `openclaw/plugin-sdk` subpaths and unresolved external packages discovered in
246
- the plugin import graph. The mock SDK captures registrations; it does not call
247
- network services, launch OpenClaw, run provider SDKs, or emulate service
248
- lifecycle side effects.
442
+ Keep this package dependency-light. Do not add runtime dependencies unless they
443
+ remove real complexity. Default checks must stay offline and credential-free.
444
+
445
+ ## Release Notes
446
+
447
+ The package publishes from annotated `v*` tags through GitHub Actions. The
448
+ release workflow runs the test suite, verifies the npm tarball, publishes the
449
+ GitHub release, and publishes the public npm package through npm trusted
450
+ publishing.
451
+
452
+ Before tagging a release:
453
+
454
+ 1. Move `CHANGELOG.md` `Unreleased` notes into a versioned section.
455
+ 2. Update `package.json` to the same version.
456
+ 3. Update Crabpot's `pluginInspectorRef` to the release commit.
457
+ 4. Run `npm run release:readiness`.
458
+ 5. Run the Crabpot plugin-inspector smoke commands printed by
459
+ `npm run release:crabpot -- --crabpot ../crabpot`.
460
+
461
+ After npm publish, update Crabpot's package pin and run:
462
+
463
+ ```bash
464
+ npm run release:crabpot -- --crabpot ../crabpot --published
465
+ ```
249
466
 
250
- Use the mock lane for plugin compatibility CI. Keep live provider/service tests
251
- in the plugin repo behind their own credentials and explicit opt-in flags.
467
+ Do not publish npm packages without explicit owner approval.
252
468
 
253
- ## Scope
469
+ ## Contribution Notes
254
470
 
255
- Default inspection is offline and credential-free. It reads manifests, package
256
- metadata, and source files, then reports observed `api.on(...)`,
257
- `api.register*`, `define*`, SDK imports, and manifest contracts.
258
- OpenClaw target checkout parsing is limited to public compatibility registries,
259
- SDK package exports, manifest types, hooks, and captured registrar metadata.
471
+ There is no `CONTRIBUTING.md` in this repository. Until one exists, use the repo
472
+ scripts above as the local contract and follow these project rules:
260
473
 
261
- Cold import capture, synthetic contract probes, and runtime capture are explicit
262
- opt-in modes. Live lanes stay credential-gated and must never run in default CI.
474
+ - preserve stable report field names and finding codes;
475
+ - prefer public OpenClaw plugin contracts over core internals;
476
+ - isolate any OpenClaw source parsing behind explicit helpers;
477
+ - keep runtime execution behind `--allow-execute` or
478
+ `PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1`;
479
+ - when behavior, entrypoints, release metadata, or the npm package version
480
+ change, update Crabpot's `@openclaw/plugin-inspector` pin/docs/smoke path and
481
+ run the Crabpot plugin-inspector smoke before calling the work done.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/plugin-inspector",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "description": "Offline compatibility inspector for OpenClaw plugins.",
6
6
  "type": "module",
@@ -43,14 +43,19 @@
43
43
  "files": [
44
44
  "src",
45
45
  "examples",
46
+ "docs/plugin-inspector-banner.jpg",
46
47
  "README.md",
47
48
  "CHANGELOG.md",
48
49
  "LICENSE"
49
50
  ],
50
51
  "scripts": {
51
- "check": "npm test && npm pack --dry-run",
52
+ "check": "npm test && npm run release:contents",
52
53
  "release:crabpot": "node scripts/check-crabpot-followthrough.mjs",
54
+ "release:contents": "node scripts/check-package-contents.mjs",
55
+ "release:plan": "node scripts/release-plan.mjs",
56
+ "release:readiness": "npm run release:local && npm run release:crabpot",
53
57
  "release:local": "npm run check",
58
+ "release:notes": "node scripts/release-notes.mjs --unreleased",
54
59
  "test": "node --test test/*.test.js"
55
60
  },
56
61
  "keywords": [
package/src/api.js CHANGED
@@ -8,6 +8,24 @@ import { renderTextSummary, writeCompatibilityReport } from "./report.js";
8
8
  import { writeCiOutputArtifacts } from "./ci-outputs.js";
9
9
  import { buildRuntimeCaptureReport, writeRuntimeCaptureReport } from "./runtime-capture-report.js";
10
10
  import { inspectCompatibilityFixtureSet, inspectFixtureSet } from "./inspector.js";
11
+ import {
12
+ buildColdImportReadiness,
13
+ renderColdImportReadinessMarkdown,
14
+ validateColdImportReadiness,
15
+ writeColdImportReadiness,
16
+ } from "./cold-import-readiness.js";
17
+ import {
18
+ buildWorkspacePlan,
19
+ renderWorkspacePlanMarkdown,
20
+ validateWorkspacePlan,
21
+ writeWorkspacePlan,
22
+ } from "./workspace-plan.js";
23
+ import {
24
+ buildPlatformProbes,
25
+ renderPlatformProbesMarkdown,
26
+ validatePlatformProbes,
27
+ writePlatformProbes,
28
+ } from "./platform-probes.js";
11
29
 
12
30
  export async function loadPluginConfig(options = {}) {
13
31
  if (options.config) {
@@ -87,6 +105,98 @@ export async function runFixtureSetReport(options = {}) {
87
105
  return { report, paths };
88
106
  }
89
107
 
108
+ export async function buildFixtureSetColdImportReadiness(options = {}) {
109
+ const config = options.report ? null : await loadFixtureSetConfig(options);
110
+ const report =
111
+ options.report ??
112
+ (await inspectCompatibilityFixtureSet(config, {
113
+ generatedAt: options.generatedAt,
114
+ openclawPath: options.openclawPath,
115
+ targetOpenClaw: options.targetOpenClaw,
116
+ }));
117
+
118
+ return buildColdImportReadiness({
119
+ ...options,
120
+ report,
121
+ rootDir: options.rootDir ?? config?.rootDir ?? options.cwd,
122
+ });
123
+ }
124
+
125
+ export function renderFixtureSetColdImportReadinessMarkdown(readiness, options = {}) {
126
+ return renderColdImportReadinessMarkdown(readiness, options);
127
+ }
128
+
129
+ export async function writeFixtureSetColdImportReadiness(readiness, options = {}) {
130
+ return writeColdImportReadiness(readiness, options);
131
+ }
132
+
133
+ export async function runFixtureSetColdImportReadiness(options = {}) {
134
+ const readiness = await buildFixtureSetColdImportReadiness(options);
135
+ const paths = options.write === false ? null : await writeFixtureSetColdImportReadiness(readiness, options);
136
+ return { readiness, paths };
137
+ }
138
+
139
+ export async function buildFixtureSetWorkspacePlan(options = {}) {
140
+ const config = options.report ? null : await loadFixtureSetConfig(options);
141
+ const report =
142
+ options.report ??
143
+ (await inspectCompatibilityFixtureSet(config, {
144
+ generatedAt: options.generatedAt,
145
+ openclawPath: options.openclawPath,
146
+ targetOpenClaw: options.targetOpenClaw,
147
+ }));
148
+ const rootDir = options.rootDir ?? config?.rootDir ?? options.cwd;
149
+ const readiness = options.readiness ?? buildColdImportReadiness({ ...options, report, rootDir });
150
+
151
+ return buildWorkspacePlan({
152
+ ...options,
153
+ report,
154
+ readiness,
155
+ rootDir,
156
+ });
157
+ }
158
+
159
+ export function renderFixtureSetWorkspacePlanMarkdown(plan, options = {}) {
160
+ return renderWorkspacePlanMarkdown(plan, options);
161
+ }
162
+
163
+ export function validateFixtureSetWorkspacePlan(plan, options = {}) {
164
+ return validateWorkspacePlan(plan, options);
165
+ }
166
+
167
+ export async function writeFixtureSetWorkspacePlan(plan, options = {}) {
168
+ return writeWorkspacePlan(plan, options);
169
+ }
170
+
171
+ export async function runFixtureSetWorkspacePlan(options = {}) {
172
+ const plan = await buildFixtureSetWorkspacePlan(options);
173
+ const paths = options.write === false ? null : await writeFixtureSetWorkspacePlan(plan, options);
174
+ return { plan, paths };
175
+ }
176
+
177
+ export async function buildFixtureSetPlatformProbes(options = {}) {
178
+ const plan = options.plan ?? (await buildFixtureSetWorkspacePlan(options));
179
+ return buildPlatformProbes({ ...options, plan });
180
+ }
181
+
182
+ export function renderFixtureSetPlatformProbesMarkdown(report, options = {}) {
183
+ return renderPlatformProbesMarkdown(report, options);
184
+ }
185
+
186
+ export function validateFixtureSetPlatformProbes(report, options = {}) {
187
+ return validatePlatformProbes(report, options);
188
+ }
189
+
190
+ export async function writeFixtureSetPlatformProbes(report, options = {}) {
191
+ return writePlatformProbes(report, options);
192
+ }
193
+
194
+ export async function runFixtureSetPlatformProbes(options = {}) {
195
+ const report = await buildFixtureSetPlatformProbes(options);
196
+ const paths = options.write === false ? null : await writeFixtureSetPlatformProbes(report, options);
197
+ return { report, paths };
198
+ }
199
+
90
200
  export async function runPluginCheck(options = {}) {
91
201
  const outDir = options.outDir ?? "reports";
92
202
  const config = await loadPluginConfig(options);
@@ -131,7 +241,7 @@ export async function setupPluginInspector(options = {}) {
131
241
  return writePluginInspectorInit(options);
132
242
  }
133
243
 
134
- export { createCaptureApi, renderTextSummary, writeCiOutputArtifacts };
244
+ export { createCaptureApi, renderTextSummary, validateColdImportReadiness, writeCiOutputArtifacts };
135
245
 
136
246
  function executionAllowed(options) {
137
247
  return options.allowExecution === true || process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED === "1";
@@ -82,6 +82,24 @@ export function createCaptureApi(options = {}) {
82
82
  }
83
83
  return api;
84
84
  },
85
+ onConversationBindingResolved(handler) {
86
+ const captureIndex =
87
+ captured.push({
88
+ kind: "hook",
89
+ name: "onConversationBindingResolved",
90
+ handlerType: typeof handler,
91
+ arguments: summarizeArguments([handler]),
92
+ }) - 1;
93
+ if (retainHandlers) {
94
+ retained.push({
95
+ kind: "hook",
96
+ name: "onConversationBindingResolved",
97
+ handler,
98
+ captureIndex,
99
+ });
100
+ }
101
+ return api;
102
+ },
85
103
  },
86
104
  {
87
105
  get(target, property) {
package/src/index.js CHANGED
@@ -1,17 +1,239 @@
1
+ import * as pluginApi from "./api.js";
2
+ import * as ciPolicyApi from "./ci-policy.js";
3
+ import * as ciSummaryApi from "./ci-summary.js";
4
+ import * as configApi from "./config.js";
5
+ import * as contractCaptureApi from "./contract-capture.js";
6
+ import * as contractCoverageApi from "./contract-coverage.js";
7
+ import * as executionResultsApi from "./execution-results.js";
8
+ import * as importLoopProfileApi from "./import-loop-profile.js";
9
+ import * as inspectorApi from "./inspector.js";
10
+ import * as issuesApi from "./issues.js";
11
+ import * as openClawTargetApi from "./openclaw-target.js";
12
+ import * as profileDiffApi from "./profile-diff.js";
13
+ import * as refDiffApi from "./ref-diff.js";
14
+ import * as reportApi from "./report.js";
15
+ import * as runtimeProfileApi from "./runtime-profile.js";
16
+ import * as syntheticProbeSuiteApi from "./synthetic-probe-suite.js";
17
+ import * as syntheticProbesApi from "./synthetic-probes.js";
18
+
19
+ export const pluginRoot = Object.freeze({
20
+ loadConfig: pluginApi.loadPluginConfig,
21
+ inspect: pluginApi.inspectPluginRoot,
22
+ runCheck: pluginApi.runPluginCheck,
23
+ captureEntrypoint: pluginApi.capturePluginEntrypoint,
24
+ setup: pluginApi.setupPluginInspector,
25
+ });
26
+
27
+ export const fixtureSuites = Object.freeze({
28
+ loadConfig: configApi.loadInspectorConfig,
29
+ inspect: pluginApi.inspectCompatibilityFixtureSetConfig,
30
+ inspectStatic: pluginApi.inspectFixtureSetConfig,
31
+ runReport: pluginApi.runFixtureSetReport,
32
+ writeReports: pluginApi.writeFixtureSetReports,
33
+ renderReport: pluginApi.renderFixtureSetMarkdownReport,
34
+ renderIssues: pluginApi.renderFixtureSetIssuesReport,
35
+ buildColdImportReadiness: pluginApi.buildFixtureSetColdImportReadiness,
36
+ runColdImportReadiness: pluginApi.runFixtureSetColdImportReadiness,
37
+ writeColdImportReadiness: pluginApi.writeFixtureSetColdImportReadiness,
38
+ buildWorkspacePlan: pluginApi.buildFixtureSetWorkspacePlan,
39
+ runWorkspacePlan: pluginApi.runFixtureSetWorkspacePlan,
40
+ writeWorkspacePlan: pluginApi.writeFixtureSetWorkspacePlan,
41
+ buildPlatformProbes: pluginApi.buildFixtureSetPlatformProbes,
42
+ runPlatformProbes: pluginApi.runFixtureSetPlatformProbes,
43
+ writePlatformProbes: pluginApi.writeFixtureSetPlatformProbes,
44
+ });
45
+
46
+ export const staticInspection = Object.freeze({
47
+ loadConfig: configApi.loadInspectorConfig,
48
+ inspectSourceText: inspectorApi.inspectSourceText,
49
+ inspectPlugin: inspectorApi.inspectPlugin,
50
+ inspectFixtureSet: inspectorApi.inspectFixtureSet,
51
+ });
52
+
53
+ export const reports = Object.freeze({
54
+ renderMarkdown: reportApi.renderMarkdownReport,
55
+ renderTextSummary: pluginApi.renderTextSummary,
56
+ write: reportApi.writeReport,
57
+ issueId: issuesApi.issueId,
58
+ classifyIssueFinding: issuesApi.classifyIssueFinding,
59
+ knownIssueCodes: issuesApi.knownIssueCodes,
60
+ openClawTargetPathCandidates: openClawTargetApi.openClawTargetPathCandidates,
61
+ });
62
+
63
+ export const contracts = Object.freeze({
64
+ buildCapture: contractCaptureApi.buildContractCapture,
65
+ writeCapture: contractCaptureApi.writeContractCapture,
66
+ renderCapture: contractCaptureApi.renderContractCaptureMarkdown,
67
+ validateCapture: contractCaptureApi.validateContractCapture,
68
+ validateCoverage: contractCoverageApi.validateContractCoverage,
69
+ defaults: Object.freeze({
70
+ registrationAssertions: contractCaptureApi.defaultRegistrationAssertions,
71
+ registrationArguments: contractCaptureApi.defaultRegistrationArguments,
72
+ hookAssertions: contractCaptureApi.defaultHookAssertions,
73
+ hookEvents: contractCaptureApi.defaultHookEvents,
74
+ hookContexts: contractCaptureApi.defaultHookContexts,
75
+ }),
76
+ });
77
+
78
+ export const ci = Object.freeze({
79
+ buildSummary: ciSummaryApi.buildCiSummary,
80
+ writeSummary: ciSummaryApi.writeCiSummary,
81
+ renderSummary: ciSummaryApi.renderCiSummaryMarkdown,
82
+ readReports: ciSummaryApi.readCiReports,
83
+ deriveStatus: ciSummaryApi.deriveCiStatus,
84
+ buildPolicyReport: ciPolicyApi.buildCiPolicyReport,
85
+ writePolicyReport: ciPolicyApi.writeCiPolicyReport,
86
+ renderPolicyReport: ciPolicyApi.renderCiPolicyMarkdown,
87
+ validatePolicy: ciPolicyApi.validateCiPolicy,
88
+ validatePolicyReport: ciPolicyApi.validateCiPolicyReport,
89
+ buildExecutionResults: executionResultsApi.buildExecutionResultsReport,
90
+ writeExecutionResults: executionResultsApi.writeExecutionResultsReport,
91
+ renderExecutionResults: executionResultsApi.renderExecutionResultsMarkdown,
92
+ writeOutputs: pluginApi.writeCiOutputArtifacts,
93
+ });
94
+
95
+ export const runtime = Object.freeze({
96
+ buildProfile: runtimeProfileApi.buildRuntimeProfile,
97
+ writeProfile: runtimeProfileApi.writeRuntimeProfile,
98
+ renderProfile: runtimeProfileApi.renderRuntimeProfileMarkdown,
99
+ validateProfile: runtimeProfileApi.validateRuntimeProfile,
100
+ buildProfileDiff: profileDiffApi.buildProfileDiff,
101
+ writeProfileDiff: profileDiffApi.writeProfileDiff,
102
+ renderProfileDiff: profileDiffApi.renderProfileDiffMarkdown,
103
+ validateProfileDiff: profileDiffApi.validateProfileDiff,
104
+ buildRefDiff: refDiffApi.buildRefDiff,
105
+ writeRefDiff: refDiffApi.writeRefDiff,
106
+ renderRefDiff: refDiffApi.renderRefDiffMarkdown,
107
+ validateRefDiff: refDiffApi.validateRefDiff,
108
+ buildImportLoopProfile: importLoopProfileApi.buildImportLoopProfile,
109
+ writeImportLoopProfile: importLoopProfileApi.writeImportLoopProfile,
110
+ renderImportLoopProfile: importLoopProfileApi.renderImportLoopProfileMarkdown,
111
+ validateImportLoopProfile: importLoopProfileApi.validateImportLoopProfile,
112
+ });
113
+
114
+ export const synthetic = Object.freeze({
115
+ buildPlan: syntheticProbesApi.buildSyntheticProbePlan,
116
+ buildPlanFromReport: syntheticProbeSuiteApi.buildSyntheticProbePlanFromReport,
117
+ writePlan: syntheticProbesApi.writeSyntheticProbePlan,
118
+ renderPlan: syntheticProbesApi.renderSyntheticProbeMarkdown,
119
+ validatePlan: syntheticProbesApi.validateSyntheticProbePlan,
120
+ runCaptured: syntheticProbesApi.runCapturedSyntheticProbes,
121
+ registrationExecutionProfiles: syntheticProbesApi.syntheticRegistrationExecutionProfiles,
122
+ defaultHookEvents: syntheticProbesApi.defaultSyntheticHookEvents,
123
+ defaultHookContexts: syntheticProbesApi.defaultSyntheticHookContexts,
124
+ defaultRegistrationArguments: syntheticProbesApi.defaultSyntheticRegistrationArguments,
125
+ });
126
+
1
127
  export {
2
128
  capturePluginEntrypoint,
129
+ buildFixtureSetColdImportReadiness,
130
+ buildFixtureSetPlatformProbes,
131
+ buildFixtureSetWorkspacePlan,
3
132
  createCaptureApi,
4
133
  inspectCompatibilityFixtureSetConfig,
5
134
  inspectFixtureSetConfig,
6
135
  inspectPluginRoot,
7
136
  loadPluginConfig,
137
+ renderFixtureSetColdImportReadinessMarkdown,
8
138
  renderFixtureSetIssuesReport,
9
139
  renderFixtureSetMarkdownReport,
140
+ renderFixtureSetPlatformProbesMarkdown,
141
+ renderFixtureSetWorkspacePlanMarkdown,
10
142
  renderTextSummary,
143
+ runFixtureSetColdImportReadiness,
144
+ runFixtureSetPlatformProbes,
11
145
  runFixtureSetReport,
146
+ runFixtureSetWorkspacePlan,
12
147
  runPluginCheck,
13
148
  setupPluginInspector,
149
+ validateColdImportReadiness,
150
+ validateFixtureSetPlatformProbes,
151
+ validateFixtureSetWorkspacePlan,
14
152
  writeCiOutputArtifacts,
153
+ writeFixtureSetColdImportReadiness,
154
+ writeFixtureSetPlatformProbes,
15
155
  writeFixtureSetReports,
156
+ writeFixtureSetWorkspacePlan,
16
157
  writePluginReports,
17
158
  } from "./api.js";
159
+ export {
160
+ buildContractCapture,
161
+ defaultHookAssertions,
162
+ defaultHookContexts,
163
+ defaultHookEvents,
164
+ defaultRegistrationArguments,
165
+ defaultRegistrationAssertions,
166
+ renderContractCaptureMarkdown,
167
+ validateContractCapture,
168
+ writeContractCapture,
169
+ } from "./contract-capture.js";
170
+ export { validateContractCoverage } from "./contract-coverage.js";
171
+ export {
172
+ buildCiPolicyReport,
173
+ defaultCiPolicyReportOptions,
174
+ renderCiPolicyMarkdown,
175
+ validateCiPolicy,
176
+ validateCiPolicyReport,
177
+ writeCiPolicyReport,
178
+ } from "./ci-policy.js";
179
+ export {
180
+ buildCiSummary,
181
+ defaultCiReportPaths,
182
+ deriveCiStatus,
183
+ readCiReports,
184
+ renderCiSummaryMarkdown,
185
+ writeCiSummary,
186
+ } from "./ci-summary.js";
187
+ export { loadInspectorConfig } from "./config.js";
188
+ export {
189
+ buildExecutionResultsReport,
190
+ defaultExecutionResultsOptions,
191
+ renderExecutionResultsMarkdown,
192
+ writeExecutionResultsReport,
193
+ } from "./execution-results.js";
194
+ export {
195
+ buildImportLoopProfile,
196
+ defaultImportLoopProfileOptions,
197
+ renderImportLoopProfileMarkdown,
198
+ validateImportLoopProfile,
199
+ writeImportLoopProfile,
200
+ } from "./import-loop-profile.js";
201
+ export { classifyIssueFinding, issueId, knownIssueCodes } from "./issues.js";
202
+ export { inspectFixtureSet, inspectPlugin, inspectSourceText } from "./inspector.js";
203
+ export { openClawTargetPathCandidates } from "./openclaw-target.js";
204
+ export {
205
+ buildProfileDiff,
206
+ defaultProfileDiffOptions,
207
+ renderProfileDiffMarkdown,
208
+ validateProfileDiff,
209
+ writeProfileDiff,
210
+ } from "./profile-diff.js";
211
+ export {
212
+ buildRefDiff,
213
+ defaultRefDiffDimensions,
214
+ defaultRefDiffOptions,
215
+ renderRefDiffMarkdown,
216
+ validateRefDiff,
217
+ writeRefDiff,
218
+ } from "./ref-diff.js";
219
+ export { renderMarkdownReport, writeReport } from "./report.js";
220
+ export {
221
+ buildRuntimeProfile,
222
+ defaultRuntimeProfileCommands,
223
+ defaultRuntimeProfileOptions,
224
+ renderRuntimeProfileMarkdown,
225
+ validateRuntimeProfile,
226
+ writeRuntimeProfile,
227
+ } from "./runtime-profile.js";
228
+ export { buildSyntheticProbePlanFromReport } from "./synthetic-probe-suite.js";
229
+ export {
230
+ buildSyntheticProbePlan,
231
+ defaultSyntheticHookContexts,
232
+ defaultSyntheticHookEvents,
233
+ defaultSyntheticRegistrationArguments,
234
+ renderSyntheticProbeMarkdown,
235
+ runCapturedSyntheticProbes,
236
+ syntheticRegistrationExecutionProfiles,
237
+ validateSyntheticProbePlan,
238
+ writeSyntheticProbePlan,
239
+ } from "./synthetic-probes.js";