@openclaw/plugin-inspector 0.0.0 → 0.1.0

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.
@@ -0,0 +1,169 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { renderPaddedMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
5
+ import { resolveFromRoot } from "./path-utils.js";
6
+ import { runProfiledProcess } from "./process-profile.js";
7
+ import { assertRunCount, percentile } from "./stats.js";
8
+
9
+ const defaultCliPath = fileURLToPath(new URL("./cli.js", import.meta.url));
10
+
11
+ export const defaultImportLoopProfileOptions = {
12
+ entrypoint: "test/fixtures/lazy-import-plugin.mjs",
13
+ generatedAt: "deterministic",
14
+ jsonPath: "reports/plugin-import-loop-profile.json",
15
+ markdownPath: "reports/plugin-import-loop-profile.md",
16
+ outputDir: ".plugin-inspector/import-loop",
17
+ reportTitle: "Plugin Import Loop Profile",
18
+ runs: 3,
19
+ };
20
+
21
+ export async function buildImportLoopProfile(options = {}) {
22
+ const rootDir = path.resolve(options.rootDir ?? process.cwd());
23
+ const runs = options.runs ?? defaultImportLoopProfileOptions.runs;
24
+ const entrypoint = options.entrypoint ?? defaultImportLoopProfileOptions.entrypoint;
25
+ assertRunCount(runs, 20);
26
+
27
+ const samples = [];
28
+ for (let index = 0; index < runs; index += 1) {
29
+ samples.push(await runCaptureSample({ ...options, entrypoint, index, rootDir }));
30
+ }
31
+
32
+ const wallMs = samples.map((sample) => sample.wallMs).sort((left, right) => left - right);
33
+ return {
34
+ generatedAt: options.generatedAt ?? defaultImportLoopProfileOptions.generatedAt,
35
+ mode: options.mode ?? "subprocess-cold-import-loop",
36
+ entrypoint,
37
+ summary: {
38
+ runs,
39
+ p50WallMs: percentile(wallMs, 0.5),
40
+ p95WallMs: percentile(wallMs, 0.95),
41
+ maxPeakRssMb: Math.max(0, ...samples.map((sample) => sample.peakRssMb)),
42
+ maxCpuMsEstimate: Math.max(0, ...samples.map((sample) => sample.cpuMsEstimate)),
43
+ capturedCount: samples.reduce((sum, sample) => sum + sample.capturedCount, 0),
44
+ failCount: samples.filter((sample) => sample.exitCode !== 0 || sample.status !== "captured").length,
45
+ },
46
+ samples,
47
+ };
48
+ }
49
+
50
+ export function validateImportLoopProfile(report) {
51
+ const errors = [];
52
+ if (report.summary.failCount > 0) {
53
+ errors.push(`import loop has ${report.summary.failCount} failed sample(s)`);
54
+ }
55
+ if (report.summary.capturedCount < report.summary.runs) {
56
+ errors.push("import loop did not capture at least one contract per run");
57
+ }
58
+ if (report.summary.p50WallMs <= 0) {
59
+ errors.push("import loop is missing wall-time samples");
60
+ }
61
+ return errors;
62
+ }
63
+
64
+ export async function writeImportLoopProfile(report, options = {}) {
65
+ const rootDir = path.resolve(options.rootDir ?? process.cwd());
66
+ const jsonPath = resolveFromRoot(rootDir, options.jsonPath ?? defaultImportLoopProfileOptions.jsonPath);
67
+ const markdownPath = resolveFromRoot(rootDir, options.markdownPath ?? defaultImportLoopProfileOptions.markdownPath);
68
+ return writeJsonMarkdownArtifacts({
69
+ jsonPath,
70
+ markdownPath,
71
+ json: report,
72
+ markdown: renderImportLoopProfileMarkdown(report, options),
73
+ check: options.check,
74
+ });
75
+ }
76
+
77
+ export function renderImportLoopProfileMarkdown(report, options = {}) {
78
+ const title = options.title ?? options.reportTitle ?? defaultImportLoopProfileOptions.reportTitle;
79
+ return [
80
+ `# ${title}`,
81
+ "",
82
+ `Generated: ${report.generatedAt}`,
83
+ `Mode: ${report.mode}`,
84
+ `Entrypoint: ${report.entrypoint}`,
85
+ "",
86
+ "## Summary",
87
+ "",
88
+ markdownTable(Object.entries(report.summary).map(([key, value]) => [key, value]), ["Metric", "Value"]),
89
+ "",
90
+ "## Samples",
91
+ "",
92
+ markdownTable(
93
+ report.samples.map((sample) => [
94
+ sample.index,
95
+ sample.status,
96
+ sample.capturedCount,
97
+ `${sample.wallMs} ms`,
98
+ `${sample.peakRssMb} MB`,
99
+ `${sample.cpuMsEstimate} ms`,
100
+ sample.exitCode,
101
+ ]),
102
+ ["Run", "Status", "Captured", "Wall", "Peak RSS", "CPU Estimate", "Exit"],
103
+ ),
104
+ ].join("\n");
105
+ }
106
+
107
+ async function runCaptureSample(options) {
108
+ const outputDir = resolveFromRoot(
109
+ options.rootDir,
110
+ options.outputDir ?? defaultImportLoopProfileOptions.outputDir,
111
+ );
112
+ const outputPath = path.join(outputDir, `capture-${options.index}.json`);
113
+ await mkdir(path.dirname(outputPath), { recursive: true });
114
+
115
+ const command = buildCaptureCommand({ ...options, outputPath });
116
+ const profile = await runProfiledProcess({
117
+ command: command.command,
118
+ args: command.args,
119
+ cwd: command.cwd ?? options.rootDir,
120
+ env: { ...process.env, ...command.env },
121
+ });
122
+ const output = profile.exitCode === 0 ? await readCaptureOutput(outputPath) : null;
123
+
124
+ return {
125
+ index: options.index,
126
+ exitCode: profile.exitCode,
127
+ status: output?.status ?? "failed",
128
+ capturedCount: output?.captured?.length ?? 0,
129
+ wallMs: profile.wallMs,
130
+ peakRssMb: profile.peakRssMb,
131
+ peakCpuPercent: profile.peakCpuPercent,
132
+ cpuMsEstimate: profile.cpuMsEstimate,
133
+ stderrPreview: profile.stderrPreview,
134
+ };
135
+ }
136
+
137
+ function buildCaptureCommand(options) {
138
+ if (typeof options.captureCommand === "function") {
139
+ return options.captureCommand({
140
+ entrypoint: options.entrypoint,
141
+ index: options.index,
142
+ outputPath: options.outputPath,
143
+ rootDir: options.rootDir,
144
+ });
145
+ }
146
+ if (options.captureScript) {
147
+ return {
148
+ command: process.execPath,
149
+ args: [options.captureScript, options.entrypoint, "--output", options.outputPath],
150
+ cwd: options.rootDir,
151
+ env: { [options.optInEnv ?? "PLUGIN_INSPECTOR_EXECUTE_ISOLATED"]: "1", ...options.captureEnv },
152
+ };
153
+ }
154
+ return {
155
+ command: process.execPath,
156
+ args: [defaultCliPath, "capture", options.entrypoint, "--output", options.outputPath],
157
+ cwd: options.rootDir,
158
+ env: { PLUGIN_INSPECTOR_EXECUTE_ISOLATED: "1", ...options.captureEnv },
159
+ };
160
+ }
161
+
162
+ async function readCaptureOutput(outputPath) {
163
+ const { readFile } = await import("node:fs/promises");
164
+ return JSON.parse(await readFile(outputPath, "utf8"));
165
+ }
166
+
167
+ function markdownTable(rows, headers) {
168
+ return renderPaddedMarkdownTable(rows, headers);
169
+ }
package/src/index.js ADDED
@@ -0,0 +1,186 @@
1
+ export {
2
+ escapeMarkdownTableCell,
3
+ renderArtifactContent,
4
+ renderMarkdownTable,
5
+ renderPaddedMarkdownTable,
6
+ writeArtifacts,
7
+ writeJsonMarkdownArtifacts,
8
+ } from "./artifacts.js";
9
+ export {
10
+ normalizeRepoPath,
11
+ posixJoin,
12
+ resolveFromRoot,
13
+ resolveRequiredFromRoot,
14
+ slugForArtifact,
15
+ toRepoPath,
16
+ } from "./path-utils.js";
17
+ export { readJsonFile, readOptionalJsonFile } from "./json-file.js";
18
+ export { assertRunCount, percentile } from "./stats.js";
19
+ export { createCaptureApi } from "./capture-api.js";
20
+ export {
21
+ buildCiPolicyReport,
22
+ defaultCiPolicyReportOptions,
23
+ renderCiPolicyMarkdown,
24
+ validateCiPolicy,
25
+ validateCiPolicyReport,
26
+ writeCiPolicyReport,
27
+ } from "./ci-policy.js";
28
+ export {
29
+ buildCiSummary,
30
+ defaultCiReportPaths,
31
+ deriveCiStatus,
32
+ readCiReports,
33
+ renderCiSummaryMarkdown,
34
+ writeCiSummary,
35
+ } from "./ci-summary.js";
36
+ export {
37
+ buildContractProbes,
38
+ contractProbeRules,
39
+ probePriority,
40
+ } from "./contract-probes.js";
41
+ export {
42
+ buildContractCapture,
43
+ defaultHookAssertions,
44
+ defaultHookContexts,
45
+ defaultHookEvents,
46
+ defaultRegistrationArguments,
47
+ defaultRegistrationAssertions,
48
+ renderContractCaptureMarkdown,
49
+ validateContractCapture,
50
+ writeContractCapture,
51
+ } from "./contract-capture.js";
52
+ export {
53
+ renderCompatibilityIssuesReport,
54
+ renderCompatibilityMarkdownReport,
55
+ } from "./compatibility-report.js";
56
+ export {
57
+ knownIssueClasses,
58
+ validateContractCoverage,
59
+ } from "./contract-coverage.js";
60
+ export {
61
+ buildColdImportReadiness,
62
+ renderColdImportReadinessMarkdown,
63
+ validateColdImportReadiness,
64
+ writeColdImportReadiness,
65
+ } from "./cold-import-readiness.js";
66
+ export {
67
+ buildIssues,
68
+ classifyIssueFinding,
69
+ deprecatedCompatRecords,
70
+ issueId,
71
+ issueMetadata,
72
+ issueMetadataByCode,
73
+ knownIssueCodes,
74
+ summarizeIssueClasses,
75
+ } from "./issues.js";
76
+ export {
77
+ buildExecutionResultsReport,
78
+ defaultExecutionResultsOptions,
79
+ renderExecutionResultsMarkdown,
80
+ writeExecutionResultsReport,
81
+ } from "./execution-results.js";
82
+ export {
83
+ buildCompatibilityFixtureReport,
84
+ classifyCompatibilityFixture,
85
+ classifyPackageContracts,
86
+ classifyTargetOpenClawCoverage,
87
+ readPackageSummaries,
88
+ readPluginManifests,
89
+ summarizePackage,
90
+ } from "./fixture-summary.js";
91
+ export {
92
+ buildImportLoopProfile,
93
+ defaultImportLoopProfileOptions,
94
+ renderImportLoopProfileMarkdown,
95
+ validateImportLoopProfile,
96
+ writeImportLoopProfile,
97
+ } from "./import-loop-profile.js";
98
+ export {
99
+ defaultOpenClawCheckoutPaths,
100
+ openClawTargetPathCandidates,
101
+ parseCompatRecordEntries,
102
+ parseExportedStringArray,
103
+ parsePluginSdkExports,
104
+ parseTypeFields,
105
+ readOpenClawTargetSurface,
106
+ } from "./openclaw-target.js";
107
+ export {
108
+ captureEntrypoint,
109
+ captureEntrypointWithMockSdk,
110
+ inspectCompatibilityFixtureSet,
111
+ inspectFixtureSet,
112
+ inspectPlugin,
113
+ inspectSourceText,
114
+ } from "./inspector.js";
115
+ export {
116
+ defaultPluginRootConfigFiles,
117
+ fixtureCheckoutPath,
118
+ fixtureSourceRoot,
119
+ loadInspectorConfig,
120
+ loadPluginRootConfig,
121
+ normalizeInspectorConfig,
122
+ normalizePluginRootConfig,
123
+ validateInspectorConfig,
124
+ } from "./config.js";
125
+ export {
126
+ buildPlatformProbes,
127
+ defaultPlatformTargets,
128
+ renderPlatformProbesMarkdown,
129
+ validatePlatformProbes,
130
+ writePlatformProbes,
131
+ } from "./platform-probes.js";
132
+ export {
133
+ buildProfileDiff,
134
+ defaultProfileDiffOptions,
135
+ renderProfileDiffMarkdown,
136
+ validateProfileDiff,
137
+ writeProfileDiff,
138
+ } from "./profile-diff.js";
139
+ export {
140
+ buildRefDiff,
141
+ defaultRefDiffDimensions,
142
+ defaultRefDiffOptions,
143
+ renderRefDiffMarkdown,
144
+ validateRefDiff,
145
+ writeRefDiff,
146
+ } from "./ref-diff.js";
147
+ export {
148
+ buildCompatibilityReport,
149
+ classifyCompatRecordCoverage,
150
+ renderMarkdownReport,
151
+ renderTextSummary,
152
+ writeCompatibilityReport,
153
+ writeReport,
154
+ } from "./report.js";
155
+ export {
156
+ buildRuntimeProfile,
157
+ defaultRuntimeProfileCommands,
158
+ defaultRuntimeProfileOptions,
159
+ renderRuntimeProfileMarkdown,
160
+ validateRuntimeProfile,
161
+ writeRuntimeProfile,
162
+ } from "./runtime-profile.js";
163
+ export {
164
+ buildRuntimeCaptureReport,
165
+ renderRuntimeCaptureMarkdown,
166
+ writeRuntimeCaptureReport,
167
+ } from "./runtime-capture-report.js";
168
+ export { createMockSdkPackage } from "./sdk-mock.js";
169
+ export {
170
+ buildSyntheticProbePlan,
171
+ defaultSyntheticHookContexts,
172
+ defaultSyntheticHookEvents,
173
+ defaultSyntheticRegistrationArguments,
174
+ renderSyntheticProbeMarkdown,
175
+ runCapturedSyntheticProbes,
176
+ syntheticRegistrationExecutionProfiles,
177
+ validateSyntheticProbePlan,
178
+ writeSyntheticProbePlan,
179
+ } from "./synthetic-probes.js";
180
+ export {
181
+ buildWorkspacePlan,
182
+ defaultWorkspacePlanOptions,
183
+ renderWorkspacePlanMarkdown,
184
+ validateWorkspacePlan,
185
+ writeWorkspacePlan,
186
+ } from "./workspace-plan.js";