@openclaw/plugin-inspector 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/README.md +9 -5
- 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/fixture-summary.js +30 -0
- package/src/index.js +1 -1
- package/src/inspector.js +18 -1
- package/src/issues.js +207 -1
- package/src/report.js +14 -9
- package/src/sdk-deprecation-rules.js +369 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.3.14 - 2026-06-11
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Flag deprecated `loadSessionStore(...)` whole-store session helper usage as an author-facing deprecation warning while keeping speculative transcript-identity migration rules out of default inspection. Thanks @jalehman.
|
|
8
|
+
|
|
9
|
+
## 0.3.13 - 2026-06-09
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Add `authorRemediation.summary` and `authorRemediation.docsUrl` guidance to author-facing compatibility issues and Markdown reports.
|
|
14
|
+
- Add `--author-facing` for `check`, `ci`, and `batch` reports while keeping default output complete for internal coverage findings.
|
|
15
|
+
- Replace the recent `--include-inspector-gaps` option with a clear error pointing to `--author-facing`.
|
|
4
16
|
|
|
5
17
|
## 0.3.12 - 2026-06-09
|
|
6
18
|
|
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. |
|
|
@@ -299,7 +299,7 @@ Important report sections:
|
|
|
299
299
|
| `status` | `pass` unless hard breakages exist. |
|
|
300
300
|
| `summary` | Counts for fixtures, breakages, warnings, suggestions, issues, issue classes, and contract probes. |
|
|
301
301
|
| `targetOpenClaw` | Status and public compatibility data read from the optional OpenClaw checkout. |
|
|
302
|
-
| `fixtures` | Per-plugin metadata, hooks, registrations, manifest contracts, package data, and SDK
|
|
302
|
+
| `fixtures` | Per-plugin metadata, hooks, registrations, manifest contracts, package data, SDK imports, and SDK deprecation evidence. |
|
|
303
303
|
| `breakages` | Blocking compatibility failures. |
|
|
304
304
|
| `warnings` / `suggestions` | Non-blocking compatibility findings. |
|
|
305
305
|
| `issues` | Normalized issue rows with severity and class. |
|
|
@@ -307,9 +307,13 @@ 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`.
|
|
314
|
+
Current author-facing deprecation warnings include deprecated SDK helpers such
|
|
315
|
+
as `loadSessionStore(...)` when a plugin still depends on the legacy whole-store
|
|
316
|
+
session shape.
|
|
313
317
|
|
|
314
318
|
## CI Policy And Shared Reporting Primitives
|
|
315
319
|
|
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/fixture-summary.js
CHANGED
|
@@ -49,6 +49,7 @@ export async function buildCompatibilityFixtureReport({ fixture, inspection, che
|
|
|
49
49
|
packages: packageSummaries,
|
|
50
50
|
sdkImports,
|
|
51
51
|
sdkImportDetails: inspection.sdkImports ?? [],
|
|
52
|
+
sdkDeprecations: inspection.sdkDeprecations ?? [],
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -496,6 +497,7 @@ export function classifyCompatibilityFixture({ fixture, inspection, fixtureRepor
|
|
|
496
497
|
logs.push(...packageContracts.logs);
|
|
497
498
|
decisions.push(...packageContracts.decisions);
|
|
498
499
|
classifySecurityManifestCoverage({ fixture, fixtureReport, warnings, decisions });
|
|
500
|
+
classifySdkDeprecations({ fixture, inspection, fixtureReport, warnings, decisions });
|
|
499
501
|
|
|
500
502
|
for (const pluginManifest of fixtureReport.pluginManifests) {
|
|
501
503
|
const providerAuthKeys = Object.keys(pluginManifest.providerAuthEnvVars ?? {});
|
|
@@ -702,6 +704,34 @@ export function classifyCompatibilityFixture({ fixture, inspection, fixtureRepor
|
|
|
702
704
|
return { warnings, suggestions, logs, decisions };
|
|
703
705
|
}
|
|
704
706
|
|
|
707
|
+
function classifySdkDeprecations({ fixture, inspection, fixtureReport, warnings, decisions }) {
|
|
708
|
+
const grouped = new Map();
|
|
709
|
+
for (const finding of fixtureReport.sdkDeprecations ?? inspection.sdkDeprecations ?? []) {
|
|
710
|
+
const existing = grouped.get(finding.code) ?? [];
|
|
711
|
+
existing.push(finding);
|
|
712
|
+
grouped.set(finding.code, existing);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
for (const [code, findings] of grouped) {
|
|
716
|
+
const first = findings[0];
|
|
717
|
+
warnings.push({
|
|
718
|
+
fixture: fixture.id,
|
|
719
|
+
code,
|
|
720
|
+
level: "warning",
|
|
721
|
+
message: first.message,
|
|
722
|
+
evidence: findings.map((finding) => `${finding.surface} @ ${finding.ref}`),
|
|
723
|
+
});
|
|
724
|
+
decisions.push({
|
|
725
|
+
fixture: fixture.id,
|
|
726
|
+
decision: "core-compat-adapter",
|
|
727
|
+
seam: "session-store",
|
|
728
|
+
action:
|
|
729
|
+
"Keep loadSessionStore compatibility active while plugin authors migrate to row-scoped session helpers.",
|
|
730
|
+
evidence: findings.map((finding) => finding.ref).join(", "),
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
705
735
|
function classifySecurityManifestCoverage({ fixture, fixtureReport, warnings, decisions }) {
|
|
706
736
|
for (const securityManifest of fixtureReport.securityManifests ?? []) {
|
|
707
737
|
warnings.push({
|
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
|
@@ -10,6 +10,7 @@ import { fixtureCheckoutPath, fixtureSourceRoot } from "./config.js";
|
|
|
10
10
|
import { buildCompatibilityFixtureReport } from "./fixture-summary.js";
|
|
11
11
|
import { readOpenClawTargetSurface } from "./openclaw-target.js";
|
|
12
12
|
import { buildCompatibilityReport, buildReport } from "./report.js";
|
|
13
|
+
import { inspectSdkDeprecations } from "./sdk-deprecation-rules.js";
|
|
13
14
|
|
|
14
15
|
const execFileAsync = promisify(execFile);
|
|
15
16
|
const registrationEquivalents = new Map([
|
|
@@ -35,9 +36,9 @@ export async function inspectCompatibilityFixtureSet(config, options = {}) {
|
|
|
35
36
|
config,
|
|
36
37
|
inspections,
|
|
37
38
|
failures,
|
|
39
|
+
authorFacing: options.authorFacing,
|
|
38
40
|
generatedAt: options.generatedAt,
|
|
39
41
|
executionResults: options.executionResults,
|
|
40
|
-
includeInspectorGaps: options.includeInspectorGaps,
|
|
41
42
|
targetOpenClaw,
|
|
42
43
|
buildFixtureReport: ({ fixture, inspection }) =>
|
|
43
44
|
buildCompatibilityFixtureReport({
|
|
@@ -104,6 +105,7 @@ export async function inspectPlugin(fixture, options = {}) {
|
|
|
104
105
|
const hookDetails = [];
|
|
105
106
|
const registrationDetails = [];
|
|
106
107
|
const sdkImportDetails = [];
|
|
108
|
+
const sdkDeprecationDetails = [];
|
|
107
109
|
|
|
108
110
|
for (const filePath of files) {
|
|
109
111
|
const text = await readFile(filePath, "utf8");
|
|
@@ -121,6 +123,9 @@ export async function inspectPlugin(fixture, options = {}) {
|
|
|
121
123
|
for (const sdkImport of sourceInspection.sdkImports) {
|
|
122
124
|
sdkImportDetails.push(sdkImport);
|
|
123
125
|
}
|
|
126
|
+
for (const sdkDeprecation of sourceInspection.sdkDeprecations) {
|
|
127
|
+
sdkDeprecationDetails.push(sdkDeprecation);
|
|
128
|
+
}
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
const manifestInspection = await readManifestContracts(config, checkoutPath, sourceRoot);
|
|
@@ -140,6 +145,7 @@ export async function inspectPlugin(fixture, options = {}) {
|
|
|
140
145
|
packageErrors: packageInspection.errors,
|
|
141
146
|
packageEntrypoints: packageInspection.entrypoints,
|
|
142
147
|
sdkImports: uniqueDetails(sdkImportDetails),
|
|
148
|
+
sdkDeprecations: uniqueSdkDeprecations(sdkDeprecationDetails),
|
|
143
149
|
sourceFiles: files.map((filePath) => path.relative(config.rootDir ?? process.cwd(), filePath)).sort(),
|
|
144
150
|
};
|
|
145
151
|
}
|
|
@@ -160,11 +166,13 @@ export function inspectSourceText(text, filePath = "source.js") {
|
|
|
160
166
|
filePath,
|
|
161
167
|
"specifier",
|
|
162
168
|
);
|
|
169
|
+
const sdkDeprecations = inspectSdkDeprecations(searchableText, filePath);
|
|
163
170
|
|
|
164
171
|
return {
|
|
165
172
|
hooks,
|
|
166
173
|
registrations,
|
|
167
174
|
sdkImports,
|
|
175
|
+
sdkDeprecations,
|
|
168
176
|
};
|
|
169
177
|
}
|
|
170
178
|
|
|
@@ -353,6 +361,7 @@ function emptyInspection(fixture, status) {
|
|
|
353
361
|
packageErrors: [],
|
|
354
362
|
packageEntrypoints: [],
|
|
355
363
|
sdkImports: [],
|
|
364
|
+
sdkDeprecations: [],
|
|
356
365
|
sourceFiles: [],
|
|
357
366
|
};
|
|
358
367
|
}
|
|
@@ -554,3 +563,11 @@ function uniqueDetails(details) {
|
|
|
554
563
|
}
|
|
555
564
|
return [...byKey.values()];
|
|
556
565
|
}
|
|
566
|
+
|
|
567
|
+
function uniqueSdkDeprecations(details) {
|
|
568
|
+
const byKey = new Map();
|
|
569
|
+
for (const detail of [...details].sort((left, right) => left.ref.localeCompare(right.ref))) {
|
|
570
|
+
byKey.set(`${detail.code}:${detail.surface}:${detail.ref}`, detail);
|
|
571
|
+
}
|
|
572
|
+
return [...byKey.values()];
|
|
573
|
+
}
|
package/src/issues.js
CHANGED
|
@@ -41,10 +41,17 @@ export const knownIssueCodes = new Set([
|
|
|
41
41
|
"runtime-tool-capture",
|
|
42
42
|
"reserved-sdk-import",
|
|
43
43
|
"security-manifest-schema-unavailable",
|
|
44
|
+
"sdk-load-session-store",
|
|
44
45
|
"sdk-export-missing",
|
|
45
46
|
"unrecognized-security-manifest",
|
|
46
47
|
]);
|
|
47
48
|
|
|
49
|
+
const authorRemediationDocsUrl = (code) => `https://docs.openclaw.ai/clawhub/plugin-validation-fixes#${code}`;
|
|
50
|
+
|
|
51
|
+
const authorRemediation = (summary) => ({ summary });
|
|
52
|
+
|
|
53
|
+
const migrationRemediation = authorRemediation;
|
|
54
|
+
|
|
48
55
|
export const issueMetadataByCode = {
|
|
49
56
|
"before-tool-call-probe": {
|
|
50
57
|
severity: "P1",
|
|
@@ -63,6 +70,13 @@ export const issueMetadataByCode = {
|
|
|
63
70
|
owner: "core",
|
|
64
71
|
decision: "core-compat-adapter",
|
|
65
72
|
title: "channelEnvVars legacy manifest metadata must stay covered",
|
|
73
|
+
authorRemediation: migrationRemediation(
|
|
74
|
+
"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.",
|
|
75
|
+
[
|
|
76
|
+
"Mirror each channel environment variable into the current setup or provider configuration metadata.",
|
|
77
|
+
"Keep channelEnvVars only as backwards compatibility for older OpenClaw versions you still support.",
|
|
78
|
+
],
|
|
79
|
+
),
|
|
66
80
|
},
|
|
67
81
|
"conversation-access-hook": {
|
|
68
82
|
severity: "P1",
|
|
@@ -75,12 +89,40 @@ export const issueMetadataByCode = {
|
|
|
75
89
|
owner: "core",
|
|
76
90
|
decision: "core-compat-adapter",
|
|
77
91
|
title: "legacy before_agent_start hook compatibility is still used",
|
|
92
|
+
authorRemediation: migrationRemediation(
|
|
93
|
+
"Replace the legacy before_agent_start hook with the current prompt/model hooks.",
|
|
94
|
+
[
|
|
95
|
+
"Move model-selection work to before_model_resolve when possible.",
|
|
96
|
+
"Move prompt mutation work to before_prompt_build.",
|
|
97
|
+
"Keep before_agent_start only if your declared compatibility range still includes OpenClaw versions that require it.",
|
|
98
|
+
],
|
|
99
|
+
),
|
|
78
100
|
},
|
|
79
101
|
"legacy-root-sdk-import": {
|
|
80
102
|
severity: "P2",
|
|
81
103
|
owner: "core",
|
|
82
104
|
decision: "core-compat-adapter",
|
|
83
105
|
title: "root plugin SDK barrel is still used by fixtures",
|
|
106
|
+
authorRemediation: migrationRemediation(
|
|
107
|
+
"Prefer focused public plugin SDK subpath imports instead of the legacy root barrel.",
|
|
108
|
+
[
|
|
109
|
+
"Replace imports from openclaw/plugin-sdk with the documented subpath for the API you use.",
|
|
110
|
+
"Keep the root import only while supporting older OpenClaw versions that do not expose the subpath.",
|
|
111
|
+
],
|
|
112
|
+
),
|
|
113
|
+
},
|
|
114
|
+
"sdk-load-session-store": {
|
|
115
|
+
severity: "P2",
|
|
116
|
+
owner: "core",
|
|
117
|
+
decision: "core-compat-adapter",
|
|
118
|
+
title: "deprecated whole-store session helper is still used",
|
|
119
|
+
authorRemediation: migrationRemediation(
|
|
120
|
+
"Replace deprecated loadSessionStore whole-store access with row-scoped session helpers.",
|
|
121
|
+
[
|
|
122
|
+
"Use getSessionEntry(...) or listSessionEntries(...) for reads instead of cloning the whole session store.",
|
|
123
|
+
"Use patchSessionEntry(...) or upsertSessionEntry(...) for writes instead of mutating and saving a whole-store object.",
|
|
124
|
+
],
|
|
125
|
+
),
|
|
84
126
|
},
|
|
85
127
|
"sdk-export-missing": {
|
|
86
128
|
severity: "P1",
|
|
@@ -93,12 +135,26 @@ export const issueMetadataByCode = {
|
|
|
93
135
|
owner: "plugin",
|
|
94
136
|
decision: "plugin-upstream-fix",
|
|
95
137
|
title: "plugin imports reserved bundled-plugin SDK compatibility subpaths",
|
|
138
|
+
authorRemediation: authorRemediation(
|
|
139
|
+
"Stop importing reserved bundled-plugin SDK compatibility paths.",
|
|
140
|
+
[
|
|
141
|
+
"Replace reserved OpenClaw internal SDK imports with documented public openclaw/plugin-sdk subpaths.",
|
|
142
|
+
"If no public API exists for the behavior, vendor a plugin-local helper or request a public OpenClaw API.",
|
|
143
|
+
],
|
|
144
|
+
),
|
|
96
145
|
},
|
|
97
146
|
"security-manifest-schema-unavailable": {
|
|
98
147
|
severity: "P3",
|
|
99
148
|
owner: "plugin",
|
|
100
149
|
decision: "plugin-upstream-fix",
|
|
101
150
|
title: "plugin security manifest references an unavailable schema",
|
|
151
|
+
authorRemediation: authorRemediation(
|
|
152
|
+
"Remove or update the unsupported security manifest schema reference.",
|
|
153
|
+
[
|
|
154
|
+
"Delete the schema URL from openclaw.security.json if it is advisory-only.",
|
|
155
|
+
"Use a documented versioned schema once OpenClaw publishes one.",
|
|
156
|
+
],
|
|
157
|
+
),
|
|
102
158
|
},
|
|
103
159
|
"missing-compat-record": {
|
|
104
160
|
severity: "P1",
|
|
@@ -117,18 +173,37 @@ export const issueMetadataByCode = {
|
|
|
117
173
|
owner: "plugin",
|
|
118
174
|
decision: "plugin-upstream-fix",
|
|
119
175
|
title: "manifest display name is missing",
|
|
176
|
+
authorRemediation: authorRemediation(
|
|
177
|
+
"Add a display name to the plugin manifest.",
|
|
178
|
+
["Set a non-empty name field in openclaw.plugin.json."],
|
|
179
|
+
'{\n "name": "My Plugin"\n}',
|
|
180
|
+
),
|
|
120
181
|
},
|
|
121
182
|
"manifest-unknown-contracts": {
|
|
122
183
|
severity: "P1",
|
|
123
184
|
owner: "plugin",
|
|
124
185
|
decision: "plugin-upstream-fix",
|
|
125
186
|
title: "manifest declares unsupported contract keys",
|
|
187
|
+
authorRemediation: authorRemediation(
|
|
188
|
+
"Remove unsupported manifest contract keys or move them to a documented OpenClaw contract field.",
|
|
189
|
+
[
|
|
190
|
+
"Compare the contracts object to the OpenClaw manifest fields supported by your target version.",
|
|
191
|
+
"Delete custom contract keys unless OpenClaw has a versioned schema for them.",
|
|
192
|
+
],
|
|
193
|
+
),
|
|
126
194
|
},
|
|
127
195
|
"manifest-unknown-fields": {
|
|
128
196
|
severity: "P2",
|
|
129
197
|
owner: "plugin",
|
|
130
198
|
decision: "plugin-upstream-fix",
|
|
131
199
|
title: "manifest uses unsupported top-level fields",
|
|
200
|
+
authorRemediation: authorRemediation(
|
|
201
|
+
"Move unsupported top-level manifest fields into supported package metadata or remove them.",
|
|
202
|
+
[
|
|
203
|
+
"Keep openclaw.plugin.json limited to fields supported by the target OpenClaw manifest schema.",
|
|
204
|
+
"Move package-level metadata into package.json openclaw metadata when that field is supported.",
|
|
205
|
+
],
|
|
206
|
+
),
|
|
132
207
|
},
|
|
133
208
|
"package-build-artifact-entrypoint": {
|
|
134
209
|
severity: "P2",
|
|
@@ -147,72 +222,159 @@ export const issueMetadataByCode = {
|
|
|
147
222
|
owner: "plugin",
|
|
148
223
|
decision: "plugin-upstream-fix",
|
|
149
224
|
title: "OpenClaw package entrypoint is missing",
|
|
225
|
+
authorRemediation: authorRemediation(
|
|
226
|
+
"Publish the entrypoint declared in OpenClaw package metadata or update the metadata to point at an existing file.",
|
|
227
|
+
[
|
|
228
|
+
"Check package.json openclaw.extensions and openclaw.runtimeExtensions.",
|
|
229
|
+
"Ensure the referenced file exists in the published artifact, usually under dist/ after build.",
|
|
230
|
+
],
|
|
231
|
+
),
|
|
150
232
|
},
|
|
151
233
|
"package-install-metadata-incomplete": {
|
|
152
234
|
severity: "P2",
|
|
153
235
|
owner: "plugin",
|
|
154
236
|
decision: "plugin-upstream-fix",
|
|
155
237
|
title: "OpenClaw package install metadata is incomplete",
|
|
238
|
+
authorRemediation: authorRemediation(
|
|
239
|
+
"Complete the OpenClaw install metadata so ClawHub can identify the install target.",
|
|
240
|
+
[
|
|
241
|
+
"Fill package.json openclaw.install with the supported release target.",
|
|
242
|
+
"Align clawhubSpec, npmSpec, and defaultChoice with the package you publish.",
|
|
243
|
+
],
|
|
244
|
+
),
|
|
156
245
|
},
|
|
157
246
|
"package-json-missing": {
|
|
158
247
|
severity: "P2",
|
|
159
248
|
owner: "plugin",
|
|
160
249
|
decision: "plugin-upstream-fix",
|
|
161
250
|
title: "package metadata is missing",
|
|
251
|
+
authorRemediation: authorRemediation(
|
|
252
|
+
"Add a package.json to the plugin package.",
|
|
253
|
+
[
|
|
254
|
+
"Include the package name and version.",
|
|
255
|
+
"Add an openclaw metadata block describing extensions, compatibility, and install details.",
|
|
256
|
+
],
|
|
257
|
+
),
|
|
162
258
|
},
|
|
163
259
|
"package-manifest-version-drift": {
|
|
164
260
|
severity: "P2",
|
|
165
261
|
owner: "plugin",
|
|
166
262
|
decision: "plugin-upstream-fix",
|
|
167
263
|
title: "package and manifest versions drift",
|
|
264
|
+
authorRemediation: authorRemediation(
|
|
265
|
+
"Align the plugin version declared in package.json and openclaw.plugin.json.",
|
|
266
|
+
[
|
|
267
|
+
"Use the same version in both files, or remove stale manifest version metadata if package.json is authoritative.",
|
|
268
|
+
"Republish with a new package version after changing published metadata.",
|
|
269
|
+
],
|
|
270
|
+
),
|
|
168
271
|
},
|
|
169
272
|
"package-min-host-version-drift": {
|
|
170
273
|
severity: "P2",
|
|
171
274
|
owner: "plugin",
|
|
172
275
|
decision: "plugin-upstream-fix",
|
|
173
276
|
title: "OpenClaw package minimum host version drifts from build target",
|
|
277
|
+
authorRemediation: authorRemediation(
|
|
278
|
+
"Set the package minimum host version to the OpenClaw version range the plugin was built and tested against.",
|
|
279
|
+
[
|
|
280
|
+
"Update package.json openclaw.install.minHostVersion or compatibility metadata.",
|
|
281
|
+
"Keep it semver-compatible with the target OpenClaw build version.",
|
|
282
|
+
],
|
|
283
|
+
),
|
|
174
284
|
},
|
|
175
285
|
"package-npm-pack-entrypoint-missing": {
|
|
176
286
|
severity: "P1",
|
|
177
287
|
owner: "plugin",
|
|
178
288
|
decision: "plugin-upstream-fix",
|
|
179
289
|
title: "advertised npm artifact is missing OpenClaw entrypoints",
|
|
290
|
+
authorRemediation: authorRemediation(
|
|
291
|
+
"Include the declared OpenClaw entrypoints in the npm-packed artifact.",
|
|
292
|
+
[
|
|
293
|
+
"Run npm pack locally and inspect the tarball contents.",
|
|
294
|
+
"Update package.json files so dist files and manifests are included.",
|
|
295
|
+
"Build before packing if the entrypoint is generated.",
|
|
296
|
+
],
|
|
297
|
+
),
|
|
180
298
|
},
|
|
181
299
|
"package-npm-pack-metadata-missing": {
|
|
182
300
|
severity: "P2",
|
|
183
301
|
owner: "plugin",
|
|
184
302
|
decision: "plugin-upstream-fix",
|
|
185
303
|
title: "advertised npm artifact is missing OpenClaw metadata",
|
|
304
|
+
authorRemediation: authorRemediation(
|
|
305
|
+
"Include OpenClaw metadata files in the npm-packed artifact.",
|
|
306
|
+
[
|
|
307
|
+
"Run npm pack locally and inspect package.json and OpenClaw manifest files.",
|
|
308
|
+
"Update package.json files so required metadata is not excluded.",
|
|
309
|
+
],
|
|
310
|
+
),
|
|
186
311
|
},
|
|
187
312
|
"package-npm-pack-unavailable": {
|
|
188
313
|
severity: "P1",
|
|
189
314
|
owner: "plugin",
|
|
190
315
|
decision: "plugin-upstream-fix",
|
|
191
316
|
title: "advertised npm artifact cannot be packed",
|
|
317
|
+
authorRemediation: authorRemediation(
|
|
318
|
+
"Make the package packable before publishing it through ClawHub.",
|
|
319
|
+
[
|
|
320
|
+
"Remove private:true if this package is intended to publish.",
|
|
321
|
+
"Ensure package.json has a valid name and version.",
|
|
322
|
+
"Fix package scripts or files entries that make npm pack fail.",
|
|
323
|
+
],
|
|
324
|
+
),
|
|
192
325
|
},
|
|
193
326
|
"package-openclaw-entry-missing": {
|
|
194
327
|
severity: "P2",
|
|
195
328
|
owner: "plugin",
|
|
196
329
|
decision: "plugin-upstream-fix",
|
|
197
330
|
title: "OpenClaw package entrypoint metadata is missing",
|
|
331
|
+
authorRemediation: authorRemediation(
|
|
332
|
+
"Declare the plugin runtime entrypoint in package.json OpenClaw metadata.",
|
|
333
|
+
[
|
|
334
|
+
"Add openclaw.extensions for extension entrypoints.",
|
|
335
|
+
"Add openclaw.runtimeExtensions when the plugin has runtime-side code.",
|
|
336
|
+
],
|
|
337
|
+
),
|
|
198
338
|
},
|
|
199
339
|
"package-openclaw-metadata-missing": {
|
|
200
340
|
severity: "P2",
|
|
201
341
|
owner: "plugin",
|
|
202
342
|
decision: "plugin-upstream-fix",
|
|
203
343
|
title: "OpenClaw package metadata is missing",
|
|
344
|
+
authorRemediation: authorRemediation(
|
|
345
|
+
"Add the package.json openclaw metadata block.",
|
|
346
|
+
[
|
|
347
|
+
"Describe extension entrypoints, plugin API compatibility, and install metadata.",
|
|
348
|
+
"Keep package metadata in sync with openclaw.plugin.json when both files are present.",
|
|
349
|
+
],
|
|
350
|
+
),
|
|
204
351
|
},
|
|
205
352
|
"package-openclaw-unsupported-metadata": {
|
|
206
353
|
severity: "P2",
|
|
207
354
|
owner: "plugin",
|
|
208
355
|
decision: "plugin-upstream-fix",
|
|
209
356
|
title: "package declares unsupported OpenClaw metadata",
|
|
357
|
+
authorRemediation: authorRemediation(
|
|
358
|
+
"Remove unsupported OpenClaw package metadata fields.",
|
|
359
|
+
[
|
|
360
|
+
"Delete openclaw.bundle and other fields not accepted by the current package schema.",
|
|
361
|
+
"Move bundle-specific data to documented manifest fields when available.",
|
|
362
|
+
],
|
|
363
|
+
),
|
|
210
364
|
},
|
|
211
365
|
"package-plugin-api-compat-missing": {
|
|
212
366
|
severity: "P2",
|
|
213
367
|
owner: "plugin",
|
|
214
368
|
decision: "plugin-upstream-fix",
|
|
215
369
|
title: "plugin API compatibility range is missing",
|
|
370
|
+
authorRemediation: authorRemediation(
|
|
371
|
+
"Declare the OpenClaw plugin API range this package supports.",
|
|
372
|
+
[
|
|
373
|
+
"Add package.json `openclaw.compat.pluginApi` with the OpenClaw plugin API range you tested.",
|
|
374
|
+
"If known, include the OpenClaw build/version used to produce the package metadata.",
|
|
375
|
+
],
|
|
376
|
+
'"openclaw": {\n "compat": {\n "pluginApi": ">=0.1.0"\n }\n}',
|
|
377
|
+
),
|
|
216
378
|
},
|
|
217
379
|
"package-typescript-source-entrypoint": {
|
|
218
380
|
severity: "P2",
|
|
@@ -225,6 +387,13 @@ export const issueMetadataByCode = {
|
|
|
225
387
|
owner: "core",
|
|
226
388
|
decision: "core-compat-adapter",
|
|
227
389
|
title: "providerAuthEnvVars legacy manifest metadata must stay covered",
|
|
390
|
+
authorRemediation: migrationRemediation(
|
|
391
|
+
"Move legacy provider authentication environment variables into current provider setup metadata.",
|
|
392
|
+
[
|
|
393
|
+
"Mirror providerAuthEnvVars into setup.providers[].envVars or the current provider-choice metadata.",
|
|
394
|
+
"Keep the legacy field only while supporting older OpenClaw versions that still read it.",
|
|
395
|
+
],
|
|
396
|
+
),
|
|
228
397
|
},
|
|
229
398
|
"registration-capture-gap": {
|
|
230
399
|
severity: "P2",
|
|
@@ -255,6 +424,13 @@ export const issueMetadataByCode = {
|
|
|
255
424
|
owner: "plugin",
|
|
256
425
|
decision: "plugin-upstream-fix",
|
|
257
426
|
title: "plugin ships an unsupported security manifest",
|
|
427
|
+
authorRemediation: authorRemediation(
|
|
428
|
+
"Remove unsupported security manifest files until OpenClaw documents a versioned security manifest schema.",
|
|
429
|
+
[
|
|
430
|
+
"Delete openclaw.security.json if it is advisory-only and not consumed by OpenClaw.",
|
|
431
|
+
"Reintroduce it only when the schema and ClawHub behavior are documented.",
|
|
432
|
+
],
|
|
433
|
+
),
|
|
258
434
|
},
|
|
259
435
|
};
|
|
260
436
|
|
|
@@ -284,6 +460,14 @@ export function buildIssues({ breakages = [], warnings = [], suggestions = [], t
|
|
|
284
460
|
evidence: finding.evidence ?? [],
|
|
285
461
|
compatRecord: finding.compatRecord ?? null,
|
|
286
462
|
runtimeCoverage: finding.runtimeCoverage ?? null,
|
|
463
|
+
...(finding.authorRemediation
|
|
464
|
+
? {
|
|
465
|
+
authorRemediation: {
|
|
466
|
+
summary: finding.authorRemediation.summary,
|
|
467
|
+
docsUrl: authorRemediationDocsUrl(finding.code),
|
|
468
|
+
},
|
|
469
|
+
}
|
|
470
|
+
: {}),
|
|
287
471
|
}));
|
|
288
472
|
}
|
|
289
473
|
|
|
@@ -305,9 +489,18 @@ export function issueMetadata(finding, targetOpenClaw) {
|
|
|
305
489
|
decision: "inspector-follow-up",
|
|
306
490
|
title: finding.message,
|
|
307
491
|
};
|
|
492
|
+
const authorMetadata = metadata.authorRemediation
|
|
493
|
+
? {
|
|
494
|
+
authorRemediation: {
|
|
495
|
+
summary: metadata.authorRemediation.summary,
|
|
496
|
+
docsUrl: authorRemediationDocsUrl(finding.code),
|
|
497
|
+
},
|
|
498
|
+
}
|
|
499
|
+
: {};
|
|
308
500
|
return {
|
|
309
501
|
...finding,
|
|
310
502
|
...metadata,
|
|
503
|
+
...authorMetadata,
|
|
311
504
|
...classifyIssueFinding(finding, targetOpenClaw, metadata),
|
|
312
505
|
};
|
|
313
506
|
}
|
|
@@ -337,6 +530,10 @@ export function isInspectorGapFinding(finding, targetOpenClaw) {
|
|
|
337
530
|
return issueMetadata(finding, targetOpenClaw).issueClass === "inspector-gap";
|
|
338
531
|
}
|
|
339
532
|
|
|
533
|
+
export function isAuthorFacingFinding(finding, targetOpenClaw) {
|
|
534
|
+
return Boolean(issueMetadata(finding, targetOpenClaw).authorRemediation);
|
|
535
|
+
}
|
|
536
|
+
|
|
340
537
|
export function summarizeIssueClasses(issues) {
|
|
341
538
|
const summary = {
|
|
342
539
|
"compat-gap": 0,
|
|
@@ -362,7 +559,16 @@ function issueClassFor(code, options) {
|
|
|
362
559
|
if (code === "missing-compat-record") {
|
|
363
560
|
return "compat-gap";
|
|
364
561
|
}
|
|
365
|
-
if (
|
|
562
|
+
if (
|
|
563
|
+
options.deprecated ||
|
|
564
|
+
[
|
|
565
|
+
"channel-env-vars",
|
|
566
|
+
"legacy-before-agent-start",
|
|
567
|
+
"legacy-root-sdk-import",
|
|
568
|
+
"provider-auth-env-vars",
|
|
569
|
+
"sdk-load-session-store",
|
|
570
|
+
].includes(code)
|
|
571
|
+
) {
|
|
366
572
|
return "deprecation-warning";
|
|
367
573
|
}
|
|
368
574
|
if (
|
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
|
|
|
@@ -26,6 +26,7 @@ export function buildReport({ config, inspections, failures = [], generatedAt =
|
|
|
26
26
|
registrations: inspection.registrations,
|
|
27
27
|
manifestContracts: inspection.manifestContracts,
|
|
28
28
|
sdkImports: inspection.sdkImports,
|
|
29
|
+
sdkDeprecations: inspection.sdkDeprecations,
|
|
29
30
|
sourceFiles: inspection.sourceFiles,
|
|
30
31
|
manifestFiles: inspection.manifestFiles,
|
|
31
32
|
packageFiles: inspection.packageFiles,
|
|
@@ -141,14 +142,16 @@ export async function buildCompatibilityReport(options = {}) {
|
|
|
141
142
|
decisions,
|
|
142
143
|
});
|
|
143
144
|
|
|
145
|
+
const visibleBreakages = filterVisibleFindings(breakages, targetOpenClaw, options);
|
|
144
146
|
const visibleWarnings = filterVisibleFindings(warnings, targetOpenClaw, options);
|
|
145
147
|
const visibleSuggestions = filterVisibleFindings(suggestions, targetOpenClaw, options);
|
|
148
|
+
const visibleDecisions = options.authorFacing === true ? [] : decisions;
|
|
146
149
|
const runtimeCoverage = applyRuntimeExecutionCoverage({
|
|
147
150
|
findings: [...visibleWarnings, ...visibleSuggestions],
|
|
148
151
|
executionResults: options.executionResults,
|
|
149
152
|
});
|
|
150
153
|
const issues = buildIssues({
|
|
151
|
-
breakages,
|
|
154
|
+
breakages: visibleBreakages,
|
|
152
155
|
warnings: visibleWarnings,
|
|
153
156
|
suggestions: visibleSuggestions,
|
|
154
157
|
targetOpenClaw,
|
|
@@ -166,14 +169,14 @@ export async function buildCompatibilityReport(options = {}) {
|
|
|
166
169
|
return {
|
|
167
170
|
generatedAt: options.generatedAt ?? "deterministic",
|
|
168
171
|
targetOpenClaw,
|
|
169
|
-
status:
|
|
172
|
+
status: visibleBreakages.length === 0 ? "pass" : "fail",
|
|
170
173
|
summary: {
|
|
171
174
|
fixtureCount: fixtureReports.length,
|
|
172
175
|
highPriorityFixtures: fixtureReports.filter((fixture) => fixture.priority === "high").length,
|
|
173
|
-
breakageCount:
|
|
176
|
+
breakageCount: visibleBreakages.length,
|
|
174
177
|
warningCount: visibleWarnings.length,
|
|
175
178
|
suggestionCount: visibleSuggestions.length,
|
|
176
|
-
decisionCount:
|
|
179
|
+
decisionCount: visibleDecisions.length,
|
|
177
180
|
logCount: logs.length,
|
|
178
181
|
issueCount: issues.length,
|
|
179
182
|
openIssueCount: openIssues.length,
|
|
@@ -195,21 +198,21 @@ export async function buildCompatibilityReport(options = {}) {
|
|
|
195
198
|
contractProbeCount: contractProbes.length,
|
|
196
199
|
},
|
|
197
200
|
fixtures: fixtureReports,
|
|
198
|
-
breakages,
|
|
201
|
+
breakages: visibleBreakages,
|
|
199
202
|
warnings: visibleWarnings,
|
|
200
203
|
suggestions: visibleSuggestions,
|
|
201
204
|
issues,
|
|
202
205
|
contractProbes,
|
|
203
206
|
logs,
|
|
204
|
-
decisions,
|
|
207
|
+
decisions: visibleDecisions,
|
|
205
208
|
};
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
function filterVisibleFindings(findings, targetOpenClaw, options) {
|
|
209
|
-
if (options.
|
|
212
|
+
if (options.authorFacing !== true) {
|
|
210
213
|
return findings;
|
|
211
214
|
}
|
|
212
|
-
return findings.filter((finding) =>
|
|
215
|
+
return findings.filter((finding) => isAuthorFacingFinding(finding, targetOpenClaw));
|
|
213
216
|
}
|
|
214
217
|
|
|
215
218
|
export function classifyCompatRecordCoverage({ targetOpenClaw, findings, suggestions, logs, decisions }) {
|
|
@@ -475,6 +478,7 @@ function defaultCompatibilityFixtureReport({ fixture, inspection }) {
|
|
|
475
478
|
packages: [],
|
|
476
479
|
sdkImports: inspection.sdkImports.map((sdkImport) => sdkImport.specifier).filter(Boolean),
|
|
477
480
|
sdkImportDetails: inspection.sdkImports,
|
|
481
|
+
sdkDeprecations: inspection.sdkDeprecations,
|
|
478
482
|
};
|
|
479
483
|
}
|
|
480
484
|
|
|
@@ -493,6 +497,7 @@ function normalizeInspection(inspection, fixture) {
|
|
|
493
497
|
packageErrors: [],
|
|
494
498
|
packageEntrypoints: [],
|
|
495
499
|
sdkImports: [],
|
|
500
|
+
sdkDeprecations: [],
|
|
496
501
|
sourceFiles: [],
|
|
497
502
|
...inspection,
|
|
498
503
|
};
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
const loadSessionStoreReplacement =
|
|
2
|
+
"getSessionEntry(...) / listSessionEntries(...) for reads and patchSessionEntry(...) / upsertSessionEntry(...) for writes";
|
|
3
|
+
|
|
4
|
+
const loadSessionStoreSpecifiers = new Set([
|
|
5
|
+
"openclaw/plugin-sdk/config-runtime",
|
|
6
|
+
"openclaw/plugin-sdk/session-store-runtime",
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
export const pluginSdkDeprecationRules = [
|
|
10
|
+
{
|
|
11
|
+
code: "sdk-load-session-store",
|
|
12
|
+
title: "deprecated whole-store session helper is still used",
|
|
13
|
+
replacement: loadSessionStoreReplacement,
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export function inspectSdkDeprecations(text, filePath = "source.js", rules = pluginSdkDeprecationRules) {
|
|
18
|
+
const findings = [];
|
|
19
|
+
|
|
20
|
+
for (const rule of rules) {
|
|
21
|
+
if (rule.code === "sdk-load-session-store") {
|
|
22
|
+
collectLoadSessionStoreDeprecations(findings, { text, filePath, rule });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return uniqueFindings(findings)
|
|
27
|
+
.sort((left, right) => left.offset - right.offset || left.surface.localeCompare(right.surface))
|
|
28
|
+
.map(({ offset, ...finding }) => finding);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function collectLoadSessionStoreDeprecations(findings, context) {
|
|
32
|
+
collectNamedImportDeprecations(findings, context);
|
|
33
|
+
collectNamedReexportDeprecations(findings, context);
|
|
34
|
+
collectNamedRequireDeprecations(findings, context);
|
|
35
|
+
collectNamespaceUsageDeprecations(findings, context);
|
|
36
|
+
collectNamespaceRequireDeprecations(findings, context);
|
|
37
|
+
collectRuntimeUsageDeprecations(findings, context);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function collectNamedImportDeprecations(findings, context) {
|
|
41
|
+
const regex =
|
|
42
|
+
/\bimport\s+(?:type\s+)?(?:[A-Za-z_$][\w$]*\s*,\s*)?{([^}]+)}\s*from\s*["'`]([^"'`]+)["'`]/g;
|
|
43
|
+
for (const match of context.text.matchAll(regex)) {
|
|
44
|
+
const specifier = match[2];
|
|
45
|
+
if (!loadSessionStoreSpecifiers.has(specifier)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
for (const binding of parseNamedBindings(match[1])) {
|
|
49
|
+
if (binding.exported !== "loadSessionStore") {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
findings.push(
|
|
53
|
+
buildFinding(context.rule, {
|
|
54
|
+
surface: `${specifier} import`,
|
|
55
|
+
sourceText: context.text,
|
|
56
|
+
filePath: context.filePath,
|
|
57
|
+
offset: (match.index ?? 0) + match[0].lastIndexOf(binding.local),
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function collectNamedReexportDeprecations(findings, context) {
|
|
65
|
+
const regex = /\bexport\s*{([^}]+)}\s*from\s*["'`]([^"'`]+)["'`]/g;
|
|
66
|
+
for (const match of context.text.matchAll(regex)) {
|
|
67
|
+
const specifier = match[2];
|
|
68
|
+
if (!loadSessionStoreSpecifiers.has(specifier)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
for (const binding of parseNamedBindings(match[1])) {
|
|
72
|
+
if (binding.exported !== "loadSessionStore") {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
findings.push(
|
|
76
|
+
buildFinding(context.rule, {
|
|
77
|
+
surface: `${specifier} re-export`,
|
|
78
|
+
sourceText: context.text,
|
|
79
|
+
filePath: context.filePath,
|
|
80
|
+
offset: (match.index ?? 0) + match[0].lastIndexOf(binding.local),
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function collectNamedRequireDeprecations(findings, context) {
|
|
88
|
+
const regex = /\b(?:const|let|var)\s+{([^}]+)}\s*=\s*require\(\s*["'`]([^"'`]+)["'`]\s*\)/g;
|
|
89
|
+
for (const match of context.text.matchAll(regex)) {
|
|
90
|
+
const specifier = match[2];
|
|
91
|
+
if (!loadSessionStoreSpecifiers.has(specifier)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
for (const binding of parseNamedBindings(match[1], { aliasSeparator: ":" })) {
|
|
95
|
+
if (binding.exported !== "loadSessionStore") {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
findings.push(
|
|
99
|
+
buildFinding(context.rule, {
|
|
100
|
+
surface: `${specifier} require`,
|
|
101
|
+
sourceText: context.text,
|
|
102
|
+
filePath: context.filePath,
|
|
103
|
+
offset: (match.index ?? 0) + match[0].lastIndexOf(binding.local),
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function collectMemberCallDeprecations(findings, context, options) {
|
|
111
|
+
forEachMethodCall(context.text, "loadSessionStore", (offset) => {
|
|
112
|
+
// Normalize transparent parentheses and optional-chained member links before matching.
|
|
113
|
+
const receiver = readNormalizedCallReceiver(context.text, offset);
|
|
114
|
+
if (!receiver || !options.receiverMatcher(receiver)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
findings.push(
|
|
118
|
+
buildFinding(context.rule, {
|
|
119
|
+
surface: options.surface,
|
|
120
|
+
sourceText: context.text,
|
|
121
|
+
filePath: context.filePath,
|
|
122
|
+
offset,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function forEachMethodCall(text, methodName, visit) {
|
|
129
|
+
let start = 0;
|
|
130
|
+
while (start < text.length) {
|
|
131
|
+
const offset = text.indexOf(methodName, start);
|
|
132
|
+
if (offset === -1) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
start = offset + methodName.length;
|
|
136
|
+
if (!isIdentifierBoundary(text, offset - 1) || !isIdentifierBoundary(text, offset + methodName.length)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (!hasCallSuffix(text, offset + methodName.length)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
visit(offset);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function readNormalizedCallReceiver(text, methodOffset) {
|
|
147
|
+
let cursor = skipWhitespaceBackward(text, methodOffset);
|
|
148
|
+
const operator = readMemberAccessOperator(text, cursor);
|
|
149
|
+
if (!operator) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
cursor = operator.start;
|
|
153
|
+
const receiverStart = findReceiverStart(text, cursor);
|
|
154
|
+
if (receiverStart === cursor) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return normalizeReceiverExpression(text.slice(receiverStart, cursor));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function readMemberAccessOperator(text, endOffset) {
|
|
161
|
+
if (endOffset >= 2 && text.slice(endOffset - 2, endOffset) === "?.") {
|
|
162
|
+
return { kind: "optional", start: endOffset - 2 };
|
|
163
|
+
}
|
|
164
|
+
if (endOffset >= 1 && text[endOffset - 1] === ".") {
|
|
165
|
+
return { kind: "direct", start: endOffset - 1 };
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function findReceiverStart(text, endOffset) {
|
|
171
|
+
let cursor = endOffset;
|
|
172
|
+
let parenDepth = 0;
|
|
173
|
+
while (cursor > 0) {
|
|
174
|
+
const char = text[cursor - 1];
|
|
175
|
+
if (char === ")") {
|
|
176
|
+
parenDepth += 1;
|
|
177
|
+
cursor -= 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (char === "(") {
|
|
181
|
+
if (parenDepth === 0) {
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
parenDepth -= 1;
|
|
185
|
+
cursor -= 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (parenDepth > 0) {
|
|
189
|
+
cursor -= 1;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (isReceiverCharacter(char)) {
|
|
193
|
+
cursor -= 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
return cursor;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function normalizeReceiverExpression(rawReceiver) {
|
|
202
|
+
let normalized = rawReceiver.replace(/\s+/g, "");
|
|
203
|
+
while (normalized.startsWith("(") && normalized.endsWith(")") && wrapsWholeExpression(normalized)) {
|
|
204
|
+
normalized = normalized.slice(1, -1);
|
|
205
|
+
}
|
|
206
|
+
return normalized.replaceAll("?.", ".");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function wrapsWholeExpression(value) {
|
|
210
|
+
let depth = 0;
|
|
211
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
212
|
+
const char = value[index];
|
|
213
|
+
if (char === "(") {
|
|
214
|
+
depth += 1;
|
|
215
|
+
} else if (char === ")") {
|
|
216
|
+
depth -= 1;
|
|
217
|
+
if (depth === 0 && index < value.length - 1) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return depth === 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function hasCallSuffix(text, startOffset) {
|
|
226
|
+
let cursor = skipWhitespaceForward(text, startOffset);
|
|
227
|
+
if (text.slice(cursor, cursor + 2) === "?.") {
|
|
228
|
+
cursor = skipWhitespaceForward(text, cursor + 2);
|
|
229
|
+
}
|
|
230
|
+
return text[cursor] === "(";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function skipWhitespaceForward(text, startOffset) {
|
|
234
|
+
let cursor = startOffset;
|
|
235
|
+
while (cursor < text.length && /\s/.test(text[cursor])) {
|
|
236
|
+
cursor += 1;
|
|
237
|
+
}
|
|
238
|
+
return cursor;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function skipWhitespaceBackward(text, endOffset) {
|
|
242
|
+
let cursor = endOffset;
|
|
243
|
+
while (cursor > 0 && /\s/.test(text[cursor - 1])) {
|
|
244
|
+
cursor -= 1;
|
|
245
|
+
}
|
|
246
|
+
return cursor;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function isReceiverCharacter(char) {
|
|
250
|
+
return /[A-Za-z0-9_$?.]/.test(char);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function isIdentifierBoundary(text, offset) {
|
|
254
|
+
if (offset < 0 || offset >= text.length) {
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
return !/[A-Za-z0-9_$]/.test(text[offset]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function isRuntimeSessionReceiver(receiver) {
|
|
261
|
+
return /^(?:[A-Za-z_$][A-Za-z0-9_$]*|this)\.runtime\.agent\.session$/.test(receiver);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function collectNamespaceUsageDeprecations(findings, context) {
|
|
265
|
+
const regex = /\bimport\s+(?:type\s+)?\*\s+as\s+([A-Za-z_$][\w$]*)\s*from\s*["'`]([^"'`]+)["'`]/g;
|
|
266
|
+
for (const match of context.text.matchAll(regex)) {
|
|
267
|
+
const local = match[1];
|
|
268
|
+
const specifier = match[2];
|
|
269
|
+
if (!loadSessionStoreSpecifiers.has(specifier)) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
collectMemberCallDeprecations(findings, context, {
|
|
273
|
+
receiverMatcher: (receiver) => receiver === local,
|
|
274
|
+
surface: `${specifier} namespace access`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function collectNamespaceRequireDeprecations(findings, context) {
|
|
280
|
+
const regex = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*require\(\s*["'`]([^"'`]+)["'`]\s*\)/g;
|
|
281
|
+
for (const match of context.text.matchAll(regex)) {
|
|
282
|
+
const local = match[1];
|
|
283
|
+
const specifier = match[2];
|
|
284
|
+
if (!loadSessionStoreSpecifiers.has(specifier)) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
collectMemberCallDeprecations(findings, context, {
|
|
288
|
+
receiverMatcher: (receiver) => receiver === local,
|
|
289
|
+
surface: `${specifier} require namespace access`,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function collectRuntimeUsageDeprecations(findings, context) {
|
|
295
|
+
collectMemberCallDeprecations(findings, context, {
|
|
296
|
+
receiverMatcher: isRuntimeSessionReceiver,
|
|
297
|
+
surface: "api.runtime.agent.session",
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function parseNamedBindings(rawBindings, options = {}) {
|
|
302
|
+
const aliasSeparator = options.aliasSeparator ?? "as";
|
|
303
|
+
return rawBindings
|
|
304
|
+
.split(",")
|
|
305
|
+
.map((binding) => binding.trim())
|
|
306
|
+
.filter(Boolean)
|
|
307
|
+
.map((binding) => binding.replace(/^type\s+/, "").trim())
|
|
308
|
+
.map((binding) => parseBindingAlias(binding, aliasSeparator))
|
|
309
|
+
.filter((binding) => binding.exported && binding.local);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function parseBindingAlias(binding, aliasSeparator) {
|
|
313
|
+
if (aliasSeparator === ":") {
|
|
314
|
+
const separatorIndex = binding.indexOf(":");
|
|
315
|
+
if (separatorIndex === -1) {
|
|
316
|
+
return {
|
|
317
|
+
exported: binding.trim(),
|
|
318
|
+
local: binding.trim(),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
exported: binding.slice(0, separatorIndex).trim(),
|
|
323
|
+
local: binding.slice(separatorIndex + 1).trim(),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const tokens = binding.trim().split(/\s+/);
|
|
328
|
+
if (tokens.length === 3 && tokens[1] === "as") {
|
|
329
|
+
return {
|
|
330
|
+
exported: tokens[0],
|
|
331
|
+
local: tokens[2],
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
exported: binding.trim(),
|
|
337
|
+
local: binding.trim(),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function buildFinding(rule, details) {
|
|
342
|
+
const refLine = lineForOffset(details.sourceText, details.offset);
|
|
343
|
+
return {
|
|
344
|
+
code: rule.code,
|
|
345
|
+
surface: details.surface,
|
|
346
|
+
replacement: rule.replacement,
|
|
347
|
+
ref: `${details.filePath}:${refLine}`,
|
|
348
|
+
message: `loadSessionStore keeps the legacy whole-store session shape; use ${rule.replacement}.`,
|
|
349
|
+
offset: details.offset,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function uniqueFindings(findings) {
|
|
354
|
+
const byKey = new Map();
|
|
355
|
+
for (const finding of findings) {
|
|
356
|
+
byKey.set(`${finding.code}:${finding.surface}:${finding.ref}`, finding);
|
|
357
|
+
}
|
|
358
|
+
return [...byKey.values()];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function lineForOffset(text, offset) {
|
|
362
|
+
let line = 1;
|
|
363
|
+
for (let index = 0; index < offset; index += 1) {
|
|
364
|
+
if (text.charCodeAt(index) === 10) {
|
|
365
|
+
line += 1;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return line;
|
|
369
|
+
}
|