@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.
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +249 -2
- package/examples/github-actions-plugin-inspector.yml +24 -0
- package/examples/plugin-inspector.config.json +15 -0
- package/package.json +55 -3
- 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 +130 -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 +186 -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
|
@@ -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";
|