@openclaw/plugin-inspector 0.0.0 → 0.1.1
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 +21 -0
- package/LICENSE +21 -0
- package/README.md +151 -2
- package/examples/github-actions-plugin-inspector.yml +24 -0
- package/examples/plugin-inspector.config.json +15 -0
- package/package.json +56 -3
- package/src/advanced.js +186 -0
- package/src/api.js +85 -0
- package/src/artifacts.js +113 -0
- package/src/capture-api.js +115 -0
- package/src/ci-policy.js +259 -0
- package/src/ci-summary.js +223 -0
- package/src/cli.js +113 -0
- package/src/cold-import-readiness.js +271 -0
- package/src/compatibility-report.js +356 -0
- package/src/config.js +182 -0
- package/src/contract-capture.js +288 -0
- package/src/contract-coverage.js +167 -0
- package/src/contract-probes.js +156 -0
- package/src/execution-results.js +297 -0
- package/src/fixture-summary.js +780 -0
- package/src/import-loop-profile.js +169 -0
- package/src/index.js +10 -0
- package/src/inspector.js +405 -0
- package/src/issues.js +366 -0
- package/src/json-file.js +10 -0
- package/src/mock-sdk-capture-runner.js +69 -0
- package/src/openclaw-target.js +179 -0
- package/src/path-utils.js +28 -0
- package/src/platform-probes.js +238 -0
- package/src/process-profile.js +117 -0
- package/src/profile-diff.js +222 -0
- package/src/ref-diff.js +335 -0
- package/src/report.js +435 -0
- package/src/runtime-capture-report.js +134 -0
- package/src/runtime-profile.js +289 -0
- package/src/sdk-mock.js +61 -0
- package/src/stats.js +13 -0
- package/src/synthetic-probes.js +544 -0
- package/src/workspace-plan.js +496 -0
package/src/report.js
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { renderMarkdownTable, writeArtifacts, writeJsonMarkdownArtifacts } from "./artifacts.js";
|
|
3
|
+
import { renderCompatibilityIssuesReport, renderCompatibilityMarkdownReport } from "./compatibility-report.js";
|
|
4
|
+
import { buildContractProbes } from "./contract-probes.js";
|
|
5
|
+
import { classifyCompatibilityFixture } from "./fixture-summary.js";
|
|
6
|
+
import { buildIssues, summarizeIssueClasses } from "./issues.js";
|
|
7
|
+
|
|
8
|
+
export function buildReport({ config, inspections, failures = [], generatedAt = "deterministic" }) {
|
|
9
|
+
const inspectionById = new Map(inspections.map((inspection) => [inspection.id, inspection]));
|
|
10
|
+
const fixtures = [];
|
|
11
|
+
const breakages = [];
|
|
12
|
+
const logs = [];
|
|
13
|
+
const decisions = [];
|
|
14
|
+
|
|
15
|
+
for (const fixture of config.fixtures) {
|
|
16
|
+
const inspection = inspectionById.get(fixture.id) ?? emptyFixtureReport(fixture);
|
|
17
|
+
fixtures.push({
|
|
18
|
+
id: fixture.id,
|
|
19
|
+
path: fixture.path,
|
|
20
|
+
priority: fixture.priority,
|
|
21
|
+
seams: fixture.seams,
|
|
22
|
+
status: inspection.status,
|
|
23
|
+
hooks: inspection.hooks,
|
|
24
|
+
registrations: inspection.registrations,
|
|
25
|
+
manifestContracts: inspection.manifestContracts,
|
|
26
|
+
sdkImports: inspection.sdkImports,
|
|
27
|
+
sourceFiles: inspection.sourceFiles,
|
|
28
|
+
manifestFiles: inspection.manifestFiles,
|
|
29
|
+
packageFiles: inspection.packageFiles,
|
|
30
|
+
packageEntrypoints: inspection.packageEntrypoints,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
logs.push({
|
|
34
|
+
fixture: fixture.id,
|
|
35
|
+
code: "seam-inventory",
|
|
36
|
+
level: "log",
|
|
37
|
+
message: `observed ${inspection.hooks.length} hooks, ${inspection.registrations.length} registrations, and ${inspection.manifestContracts.length} manifest contracts`,
|
|
38
|
+
evidence: [
|
|
39
|
+
...inspection.hooks.map((hook) => `hook:${hook}`),
|
|
40
|
+
...inspection.registrations.map((registration) => `registration:${registration}`),
|
|
41
|
+
...inspection.manifestContracts.map((contract) => `manifestContract:${contract}`),
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const failure of failures) {
|
|
47
|
+
const fixture = failure.split(":")[0] || "unknown";
|
|
48
|
+
breakages.push({
|
|
49
|
+
fixture,
|
|
50
|
+
code: "missing-expected-seam",
|
|
51
|
+
level: "breakage",
|
|
52
|
+
message: failure,
|
|
53
|
+
evidence: [failure],
|
|
54
|
+
});
|
|
55
|
+
decisions.push({
|
|
56
|
+
fixture,
|
|
57
|
+
decision: "inspector-follow-up",
|
|
58
|
+
seam: "expected-seam",
|
|
59
|
+
action: "Investigate whether OpenClaw removed a plugin-facing contract or the fixture pin changed behavior.",
|
|
60
|
+
evidence: failure,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
generatedAt,
|
|
66
|
+
status: breakages.length === 0 ? "pass" : "fail",
|
|
67
|
+
summary: {
|
|
68
|
+
fixtureCount: fixtures.length,
|
|
69
|
+
highPriorityFixtures: fixtures.filter((fixture) => fixture.priority === "high").length,
|
|
70
|
+
breakageCount: breakages.length,
|
|
71
|
+
warningCount: 0,
|
|
72
|
+
suggestionCount: 0,
|
|
73
|
+
decisionCount: decisions.length,
|
|
74
|
+
logCount: logs.length,
|
|
75
|
+
},
|
|
76
|
+
fixtures,
|
|
77
|
+
breakages,
|
|
78
|
+
warnings: [],
|
|
79
|
+
suggestions: [],
|
|
80
|
+
logs,
|
|
81
|
+
decisions,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function buildCompatibilityReport(options = {}) {
|
|
86
|
+
const fixtureInputs = options.fixtures ?? options.config?.fixtures ?? [];
|
|
87
|
+
const targetOpenClaw = options.targetOpenClaw ?? emptyTargetOpenClaw();
|
|
88
|
+
const inspectionById = new Map((options.inspections ?? []).map((inspection) => [inspection.id, inspection]));
|
|
89
|
+
const fixtureReports = [];
|
|
90
|
+
const breakages = [];
|
|
91
|
+
const warnings = [];
|
|
92
|
+
const suggestions = [];
|
|
93
|
+
const logs = [];
|
|
94
|
+
const decisions = [];
|
|
95
|
+
const buildFixtureReport = options.buildFixtureReport ?? defaultCompatibilityFixtureReport;
|
|
96
|
+
|
|
97
|
+
for (const fixture of fixtureInputs) {
|
|
98
|
+
const inspection = normalizeInspection(inspectionById.get(fixture.id), fixture);
|
|
99
|
+
const fixtureReport = await buildFixtureReport({ fixture, inspection, targetOpenClaw });
|
|
100
|
+
fixtureReports.push(fixtureReport);
|
|
101
|
+
|
|
102
|
+
logs.push(seamInventoryFinding(fixture, inspection));
|
|
103
|
+
|
|
104
|
+
const fixtureClassification = classifyCompatibilityFixture({
|
|
105
|
+
fixture,
|
|
106
|
+
inspection,
|
|
107
|
+
fixtureReport,
|
|
108
|
+
targetOpenClaw,
|
|
109
|
+
});
|
|
110
|
+
warnings.push(...fixtureClassification.warnings);
|
|
111
|
+
suggestions.push(...fixtureClassification.suggestions);
|
|
112
|
+
logs.push(...fixtureClassification.logs);
|
|
113
|
+
decisions.push(...fixtureClassification.decisions);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const failure of options.failures ?? []) {
|
|
117
|
+
const fixture = failure.split(":")[0] || "unknown";
|
|
118
|
+
breakages.push({
|
|
119
|
+
fixture,
|
|
120
|
+
code: "missing-expected-seam",
|
|
121
|
+
level: "breakage",
|
|
122
|
+
message: failure,
|
|
123
|
+
evidence: [failure],
|
|
124
|
+
});
|
|
125
|
+
decisions.push({
|
|
126
|
+
fixture,
|
|
127
|
+
decision: "inspector-follow-up",
|
|
128
|
+
seam: "expected-seam",
|
|
129
|
+
action: "Investigate whether OpenClaw removed a plugin-facing contract or the fixture pin changed upstream behavior.",
|
|
130
|
+
evidence: failure,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
classifyCompatRecordCoverage({
|
|
135
|
+
targetOpenClaw,
|
|
136
|
+
findings: [...warnings, ...suggestions],
|
|
137
|
+
suggestions,
|
|
138
|
+
logs,
|
|
139
|
+
decisions,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const issues = buildIssues({
|
|
143
|
+
breakages,
|
|
144
|
+
warnings,
|
|
145
|
+
suggestions,
|
|
146
|
+
targetOpenClaw,
|
|
147
|
+
idPrefix: options.issueIdPrefix,
|
|
148
|
+
});
|
|
149
|
+
const contractProbes = buildContractProbes({ warnings, suggestions, fixtures: fixtureReports });
|
|
150
|
+
const issueSummary = summarizeIssueClasses(issues);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
generatedAt: options.generatedAt ?? "deterministic",
|
|
154
|
+
targetOpenClaw,
|
|
155
|
+
status: breakages.length === 0 ? "pass" : "fail",
|
|
156
|
+
summary: {
|
|
157
|
+
fixtureCount: fixtureReports.length,
|
|
158
|
+
highPriorityFixtures: fixtureReports.filter((fixture) => fixture.priority === "high").length,
|
|
159
|
+
breakageCount: breakages.length,
|
|
160
|
+
warningCount: warnings.length,
|
|
161
|
+
suggestionCount: suggestions.length,
|
|
162
|
+
decisionCount: decisions.length,
|
|
163
|
+
issueCount: issues.length,
|
|
164
|
+
p0IssueCount: issues.filter((issue) => issue.severity === "P0").length,
|
|
165
|
+
p1IssueCount: issues.filter((issue) => issue.severity === "P1").length,
|
|
166
|
+
liveIssueCount: issueSummary["live-issue"],
|
|
167
|
+
liveP0IssueCount: issues.filter((issue) => issue.issueClass === "live-issue" && issue.severity === "P0").length,
|
|
168
|
+
compatGapCount: issueSummary["compat-gap"],
|
|
169
|
+
deprecationWarningCount: issueSummary["deprecation-warning"],
|
|
170
|
+
inspectorGapCount: issueSummary["inspector-gap"],
|
|
171
|
+
upstreamIssueCount: issueSummary["upstream-metadata"],
|
|
172
|
+
fixtureRegressionCount: issueSummary["fixture-regression"],
|
|
173
|
+
contractProbeCount: contractProbes.length,
|
|
174
|
+
},
|
|
175
|
+
fixtures: fixtureReports,
|
|
176
|
+
breakages,
|
|
177
|
+
warnings,
|
|
178
|
+
suggestions,
|
|
179
|
+
issues,
|
|
180
|
+
contractProbes,
|
|
181
|
+
logs,
|
|
182
|
+
decisions,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function classifyCompatRecordCoverage({ targetOpenClaw, findings, suggestions, logs, decisions }) {
|
|
187
|
+
if (targetOpenClaw.status !== "ok") {
|
|
188
|
+
logs.push({
|
|
189
|
+
fixture: "openclaw",
|
|
190
|
+
code: "target-openclaw-unavailable",
|
|
191
|
+
level: "log",
|
|
192
|
+
message: "target OpenClaw checkout was not available, so compat record coverage was not checked",
|
|
193
|
+
evidence: [targetOpenClaw.configuredPath ?? "not configured"],
|
|
194
|
+
});
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const knownRecords = new Set(targetOpenClaw.compatRecords ?? []);
|
|
199
|
+
for (const finding of findings.filter((item) => item.compatRecord)) {
|
|
200
|
+
if (knownRecords.has(finding.compatRecord)) {
|
|
201
|
+
logs.push({
|
|
202
|
+
fixture: finding.fixture,
|
|
203
|
+
code: "compat-record-present",
|
|
204
|
+
level: "log",
|
|
205
|
+
message: "target OpenClaw checkout has a matching compat registry record",
|
|
206
|
+
evidence: [finding.compatRecord, `status:${targetOpenClaw.compatRecordStatuses?.[finding.compatRecord] ?? "unknown"}`],
|
|
207
|
+
compatRecord: finding.compatRecord,
|
|
208
|
+
});
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
suggestions.push({
|
|
213
|
+
fixture: finding.fixture,
|
|
214
|
+
code: "missing-compat-record",
|
|
215
|
+
level: "suggestion",
|
|
216
|
+
message: "fixture depends on a compatibility behavior that is not represented in the target compat registry",
|
|
217
|
+
evidence: [finding.compatRecord],
|
|
218
|
+
compatRecord: finding.compatRecord,
|
|
219
|
+
});
|
|
220
|
+
decisions.push({
|
|
221
|
+
fixture: finding.fixture,
|
|
222
|
+
decision: "core-compat-adapter",
|
|
223
|
+
seam: "compat-registry",
|
|
224
|
+
action: "Add or restore a machine-readable OpenClaw compat record before changing this plugin-facing behavior.",
|
|
225
|
+
evidence: finding.compatRecord,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function writeReport(report, options = {}) {
|
|
231
|
+
const outDir = path.resolve(options.cwd ?? process.cwd(), options.outDir ?? "reports");
|
|
232
|
+
const basename = options.basename ?? "plugin-inspector-report";
|
|
233
|
+
const jsonPath = path.join(outDir, `${basename}.json`);
|
|
234
|
+
const markdownPath = path.join(outDir, `${basename}.md`);
|
|
235
|
+
|
|
236
|
+
return writeJsonMarkdownArtifacts({
|
|
237
|
+
jsonPath,
|
|
238
|
+
markdownPath,
|
|
239
|
+
json: report,
|
|
240
|
+
markdown: renderMarkdownReport(report),
|
|
241
|
+
check: options.check,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function writeCompatibilityReport(report, options = {}) {
|
|
246
|
+
const outDir = path.resolve(options.cwd ?? process.cwd(), options.outDir ?? "reports");
|
|
247
|
+
const basename = options.basename ?? "plugin-inspector-report";
|
|
248
|
+
const jsonPath = path.join(outDir, `${basename}.json`);
|
|
249
|
+
const markdownPath = path.join(outDir, `${basename}.md`);
|
|
250
|
+
const issuesPath = path.join(outDir, options.issuesBasename ?? "plugin-inspector-issues.md");
|
|
251
|
+
|
|
252
|
+
return writeArtifacts(
|
|
253
|
+
[
|
|
254
|
+
{ name: "jsonPath", path: jsonPath, json: report },
|
|
255
|
+
{ name: "markdownPath", path: markdownPath, markdown: renderCompatibilityMarkdownReport(report) },
|
|
256
|
+
{ name: "issuesPath", path: issuesPath, markdown: renderCompatibilityIssuesReport(report) },
|
|
257
|
+
],
|
|
258
|
+
{ check: options.check },
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function renderTextSummary(report) {
|
|
263
|
+
return [
|
|
264
|
+
`Status: ${report.status.toUpperCase()}`,
|
|
265
|
+
`Fixtures: ${report.summary.fixtureCount}`,
|
|
266
|
+
`Breakages: ${report.summary.breakageCount}`,
|
|
267
|
+
`Logs: ${report.summary.logCount}`,
|
|
268
|
+
].join("\n");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function renderMarkdownReport(report) {
|
|
272
|
+
return [
|
|
273
|
+
"# OpenClaw Plugin Inspector Report",
|
|
274
|
+
"",
|
|
275
|
+
`Generated: ${report.generatedAt}`,
|
|
276
|
+
`Status: ${report.status.toUpperCase()}`,
|
|
277
|
+
"",
|
|
278
|
+
"## Summary",
|
|
279
|
+
"",
|
|
280
|
+
markdownTable(
|
|
281
|
+
[
|
|
282
|
+
["Fixtures", report.summary.fixtureCount],
|
|
283
|
+
["High-priority fixtures", report.summary.highPriorityFixtures],
|
|
284
|
+
["Hard breakages", report.summary.breakageCount],
|
|
285
|
+
["Warnings", report.summary.warningCount],
|
|
286
|
+
["Suggestions", report.summary.suggestionCount],
|
|
287
|
+
["Decision rows", report.summary.decisionCount],
|
|
288
|
+
],
|
|
289
|
+
["Metric", "Value"],
|
|
290
|
+
),
|
|
291
|
+
"",
|
|
292
|
+
"## Hard Breakages",
|
|
293
|
+
"",
|
|
294
|
+
findingsTable(report.breakages),
|
|
295
|
+
"",
|
|
296
|
+
"## Fixture Seam Inventory",
|
|
297
|
+
"",
|
|
298
|
+
markdownTable(
|
|
299
|
+
report.fixtures.map((fixture) => [
|
|
300
|
+
fixture.id,
|
|
301
|
+
fixture.priority,
|
|
302
|
+
fixture.seams.join(", "),
|
|
303
|
+
fixture.hooks.join(", ") || "-",
|
|
304
|
+
fixture.registrations.join(", ") || "-",
|
|
305
|
+
fixture.manifestContracts.join(", ") || "-",
|
|
306
|
+
]),
|
|
307
|
+
["Fixture", "Priority", "Seams", "Hooks", "Registrations", "Manifest contracts"],
|
|
308
|
+
),
|
|
309
|
+
"",
|
|
310
|
+
"## Decision Matrix",
|
|
311
|
+
"",
|
|
312
|
+
markdownTable(
|
|
313
|
+
report.decisions.map((decision) => [
|
|
314
|
+
decision.fixture,
|
|
315
|
+
decision.decision,
|
|
316
|
+
decision.seam,
|
|
317
|
+
decision.action,
|
|
318
|
+
decision.evidence,
|
|
319
|
+
]),
|
|
320
|
+
["Fixture", "Decision", "Seam", "Action", "Evidence"],
|
|
321
|
+
),
|
|
322
|
+
"",
|
|
323
|
+
"## Raw Logs",
|
|
324
|
+
"",
|
|
325
|
+
findingsTable(report.logs),
|
|
326
|
+
].join("\n");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function findingsTable(findings) {
|
|
330
|
+
if (findings.length === 0) {
|
|
331
|
+
return "_None._";
|
|
332
|
+
}
|
|
333
|
+
return markdownTable(
|
|
334
|
+
findings.map((finding) => [
|
|
335
|
+
finding.fixture,
|
|
336
|
+
finding.level,
|
|
337
|
+
finding.code,
|
|
338
|
+
finding.message,
|
|
339
|
+
(finding.evidence ?? []).join("<br>") || "-",
|
|
340
|
+
]),
|
|
341
|
+
["Fixture", "Level", "Code", "Message", "Evidence"],
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function markdownTable(rows, headers) {
|
|
346
|
+
return renderMarkdownTable(rows, headers);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function emptyFixtureReport(fixture) {
|
|
350
|
+
return {
|
|
351
|
+
id: fixture.id,
|
|
352
|
+
status: "missing",
|
|
353
|
+
hooks: [],
|
|
354
|
+
registrations: [],
|
|
355
|
+
manifestContracts: [],
|
|
356
|
+
sdkImports: [],
|
|
357
|
+
sourceFiles: [],
|
|
358
|
+
manifestFiles: [],
|
|
359
|
+
packageFiles: [],
|
|
360
|
+
packageEntrypoints: [],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function defaultCompatibilityFixtureReport({ fixture, inspection }) {
|
|
365
|
+
return {
|
|
366
|
+
id: fixture.id,
|
|
367
|
+
name: fixture.name,
|
|
368
|
+
path: fixture.path,
|
|
369
|
+
priority: fixture.priority,
|
|
370
|
+
seams: fixture.seams,
|
|
371
|
+
why: fixture.why,
|
|
372
|
+
status: inspection.status,
|
|
373
|
+
hooks: inspection.hooks,
|
|
374
|
+
hookDetails: inspection.hookDetails,
|
|
375
|
+
registrations: inspection.registrations,
|
|
376
|
+
registrationDetails: inspection.registrationDetails,
|
|
377
|
+
manifestContracts: inspection.manifestContracts,
|
|
378
|
+
manifestFiles: inspection.manifestFiles,
|
|
379
|
+
sourceFiles: inspection.sourceFiles,
|
|
380
|
+
pluginManifests: [],
|
|
381
|
+
package: null,
|
|
382
|
+
packages: [],
|
|
383
|
+
sdkImports: inspection.sdkImports.map((sdkImport) => sdkImport.specifier).filter(Boolean),
|
|
384
|
+
sdkImportDetails: inspection.sdkImports,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function normalizeInspection(inspection, fixture) {
|
|
389
|
+
return {
|
|
390
|
+
id: fixture.id,
|
|
391
|
+
status: "missing",
|
|
392
|
+
hooks: [],
|
|
393
|
+
hookDetails: [],
|
|
394
|
+
registrations: [],
|
|
395
|
+
registrationDetails: [],
|
|
396
|
+
manifestContracts: [],
|
|
397
|
+
manifestFiles: [],
|
|
398
|
+
manifestErrors: [],
|
|
399
|
+
packageFiles: [],
|
|
400
|
+
packageErrors: [],
|
|
401
|
+
packageEntrypoints: [],
|
|
402
|
+
sdkImports: [],
|
|
403
|
+
sourceFiles: [],
|
|
404
|
+
...inspection,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function seamInventoryFinding(fixture, inspection) {
|
|
409
|
+
return {
|
|
410
|
+
fixture: fixture.id,
|
|
411
|
+
code: "seam-inventory",
|
|
412
|
+
level: "log",
|
|
413
|
+
message: `observed ${inspection.hooks.length} hooks, ${inspection.registrations.length} registrations, and ${inspection.manifestContracts.length} manifest contracts`,
|
|
414
|
+
evidence: [
|
|
415
|
+
...inspection.hooks.map((hook) => `hook:${hook}`),
|
|
416
|
+
...inspection.registrations.map((registration) => `registration:${registration}`),
|
|
417
|
+
...inspection.manifestContracts.map((contract) => `manifestContract:${contract}`),
|
|
418
|
+
],
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function emptyTargetOpenClaw() {
|
|
423
|
+
return {
|
|
424
|
+
configuredPath: null,
|
|
425
|
+
status: "not-configured",
|
|
426
|
+
compatRecords: [],
|
|
427
|
+
compatRecordStatuses: {},
|
|
428
|
+
hookNames: [],
|
|
429
|
+
apiRegistrars: [],
|
|
430
|
+
capturedRegistrars: [],
|
|
431
|
+
sdkExports: [],
|
|
432
|
+
manifestFields: [],
|
|
433
|
+
manifestContractFields: [],
|
|
434
|
+
};
|
|
435
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { renderMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
|
|
3
|
+
import { captureEntrypoint } from "./inspector.js";
|
|
4
|
+
|
|
5
|
+
export async function buildRuntimeCaptureReport(options = {}) {
|
|
6
|
+
const report = options.report;
|
|
7
|
+
if (!report) {
|
|
8
|
+
throw new TypeError("buildRuntimeCaptureReport requires a compatibility report");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const rootDir = path.resolve(options.rootDir ?? process.cwd());
|
|
12
|
+
const results = [];
|
|
13
|
+
for (const fixture of report.fixtures) {
|
|
14
|
+
for (const target of captureTargets(fixture, rootDir)) {
|
|
15
|
+
results.push(await captureTarget(target, options));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
generatedAt: options.generatedAt ?? report.generatedAt,
|
|
21
|
+
mode: {
|
|
22
|
+
mockSdk: options.mockSdk !== false,
|
|
23
|
+
isolated: true,
|
|
24
|
+
},
|
|
25
|
+
summary: {
|
|
26
|
+
targetCount: results.length,
|
|
27
|
+
capturedCount: results.filter((result) => result.status === "captured").length,
|
|
28
|
+
skippedCount: results.filter((result) => result.status.startsWith("skipped")).length,
|
|
29
|
+
failedCount: results.filter((result) => result.status === "error").length,
|
|
30
|
+
registrationCount: results.flatMap((result) => result.captured ?? []).filter((item) => item.kind === "registration")
|
|
31
|
+
.length,
|
|
32
|
+
hookCount: results.flatMap((result) => result.captured ?? []).filter((item) => item.kind === "hook").length,
|
|
33
|
+
},
|
|
34
|
+
results,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function writeRuntimeCaptureReport(captureReport, options = {}) {
|
|
39
|
+
return writeJsonMarkdownArtifacts({
|
|
40
|
+
jsonPath: options.jsonPath ?? path.join(process.cwd(), "reports/plugin-inspector-runtime-capture.json"),
|
|
41
|
+
markdownPath: options.markdownPath ?? path.join(process.cwd(), "reports/plugin-inspector-runtime-capture.md"),
|
|
42
|
+
json: captureReport,
|
|
43
|
+
markdown: renderRuntimeCaptureMarkdown(captureReport, options),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function renderRuntimeCaptureMarkdown(captureReport, options = {}) {
|
|
48
|
+
return [
|
|
49
|
+
`# ${options.title ?? "Plugin Inspector Runtime Capture"}`,
|
|
50
|
+
"",
|
|
51
|
+
`Generated: ${captureReport.generatedAt}`,
|
|
52
|
+
"",
|
|
53
|
+
"## Summary",
|
|
54
|
+
"",
|
|
55
|
+
markdownTable(
|
|
56
|
+
[
|
|
57
|
+
["Targets", captureReport.summary.targetCount],
|
|
58
|
+
["Captured", captureReport.summary.capturedCount],
|
|
59
|
+
["Skipped", captureReport.summary.skippedCount],
|
|
60
|
+
["Failed", captureReport.summary.failedCount],
|
|
61
|
+
["Registrations", captureReport.summary.registrationCount],
|
|
62
|
+
["Hooks", captureReport.summary.hookCount],
|
|
63
|
+
],
|
|
64
|
+
["Metric", "Value"],
|
|
65
|
+
),
|
|
66
|
+
"",
|
|
67
|
+
"## Entrypoints",
|
|
68
|
+
"",
|
|
69
|
+
markdownTable(
|
|
70
|
+
captureReport.results.map((result) => [
|
|
71
|
+
result.fixture,
|
|
72
|
+
result.status,
|
|
73
|
+
result.entrypoint,
|
|
74
|
+
(result.captured ?? []).map((item) => `${item.kind}:${item.name}`).join(", ") || result.error || "-",
|
|
75
|
+
]),
|
|
76
|
+
["Fixture", "Status", "Entrypoint", "Captured"],
|
|
77
|
+
),
|
|
78
|
+
].join("\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function captureTargets(fixture, rootDir) {
|
|
82
|
+
return fixture.packages.flatMap((packageSummary) => {
|
|
83
|
+
const packageRoot = path.dirname(path.resolve(rootDir, packageSummary.path));
|
|
84
|
+
return (packageSummary.openclaw?.entrypoints ?? []).map((entrypoint) => ({
|
|
85
|
+
fixture: fixture.id,
|
|
86
|
+
packagePath: packageSummary.path,
|
|
87
|
+
packageRoot,
|
|
88
|
+
entrypoint,
|
|
89
|
+
entrypointPath: path.resolve(rootDir, entrypoint.relativePath),
|
|
90
|
+
rootDir,
|
|
91
|
+
}));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function captureTarget(target, options) {
|
|
96
|
+
if (!target.entrypoint.exists) {
|
|
97
|
+
return {
|
|
98
|
+
fixture: target.fixture,
|
|
99
|
+
status: "skipped-missing",
|
|
100
|
+
packagePath: target.packagePath,
|
|
101
|
+
entrypoint: target.entrypoint.relativePath,
|
|
102
|
+
captured: [],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = await captureEntrypoint(path.relative(target.rootDir, target.entrypointPath), {
|
|
108
|
+
cwd: target.rootDir,
|
|
109
|
+
pluginRoot: target.packageRoot,
|
|
110
|
+
mockSdk: options.mockSdk !== false,
|
|
111
|
+
apiOptions: options.apiOptions,
|
|
112
|
+
env: options.env,
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
fixture: target.fixture,
|
|
116
|
+
packagePath: target.packagePath,
|
|
117
|
+
entrypoint: target.entrypoint.relativePath,
|
|
118
|
+
...result,
|
|
119
|
+
};
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
fixture: target.fixture,
|
|
123
|
+
status: "error",
|
|
124
|
+
packagePath: target.packagePath,
|
|
125
|
+
entrypoint: target.entrypoint.relativePath,
|
|
126
|
+
error: error.message,
|
|
127
|
+
captured: [],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function markdownTable(rows, headers) {
|
|
133
|
+
return renderMarkdownTable(rows, headers);
|
|
134
|
+
}
|