fix(reports): quiet resolved package entrypoint P0s

Fix compatibility report classification so built runtime package entrypoints satisfy source-form OpenClaw metadata, SDK export alias misses collapse into a single compat-gap row, and P0 live issues are not repeated under the general live section.
This commit is contained in:
Vincent Koc 2026-05-05 00:34:18 -07:00 committed by GitHub
parent 68e10e0aaa
commit f642fb5c9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 120 additions and 15 deletions

View File

@ -2,6 +2,10 @@
## Unreleased
### Fixed
- Stop classifying package source entrypoints as missing when the published package provides built runtime entrypoints, and collapse SDK alias findings into a single compat-gap row.
## 0.3.10 - 2026-05-03
### Fixed

View File

@ -58,9 +58,9 @@ export function renderCompatibilityMarkdownReport(report, options = {}) {
options,
),
"",
"## Live Issues",
"## Other Live Issues",
"",
issuesTable(report.issues.filter((issue) => issue.issueClass === "live-issue"), options),
issuesTable(report.issues.filter((issue) => issue.issueClass === "live-issue" && issue.severity !== "P0"), options),
"",
"## Compat Gaps",
"",
@ -183,9 +183,9 @@ export function renderCompatibilityIssuesReport(report, options = {}) {
options,
),
"",
"## Live Issues",
"## Other Live Issues",
"",
issuesTable(report.issues.filter((issue) => issue.issueClass === "live-issue"), options),
issuesTable(report.issues.filter((issue) => issue.issueClass === "live-issue" && issue.severity !== "P0"), options),
"",
"## Compat Gaps",
"",

View File

@ -340,9 +340,12 @@ export function classifyPackageContracts({ fixture, inspection, fixtureReport })
});
}
const missingEntrypoints = packageSummary.openclaw?.entrypoints.filter((entrypoint) => !entrypoint.exists) ?? [];
const entrypoints = packageSummary.openclaw?.entrypoints ?? [];
const missingEntrypoints = entrypoints.filter((entrypoint) => !entrypoint.exists);
const buildEntrypoints = missingEntrypoints.filter((entrypoint) => entrypoint.requiresBuild);
const plainMissingEntrypoints = missingEntrypoints.filter((entrypoint) => !entrypoint.requiresBuild);
const plainMissingEntrypoints = missingEntrypoints.filter(
(entrypoint) => !entrypoint.requiresBuild && !hasUsablePackageRuntimeEntrypoint(entrypoint, packageSummary, entrypoints),
);
if (buildEntrypoints.length > 0) {
suggestions.push({
@ -920,6 +923,46 @@ function collectOpenClawEntrypoints(packageDir, openclaw, options) {
});
}
function hasUsablePackageRuntimeEntrypoint(entrypoint, packageSummary, entrypoints) {
if (!isSourceEntrypoint(entrypoint.specifier)) {
return false;
}
const runtimeBuildSpecifier = runtimeBuildSpecifierFor(entrypoint.specifier);
if (
entrypoints.some(
(candidate) =>
candidate.exists &&
candidate.requiresBuild &&
normalizeEntrypointSpecifier(candidate.specifier) === normalizeEntrypointSpecifier(runtimeBuildSpecifier),
)
) {
return true;
}
if (entrypoint.kind === "extension" && entrypoints.some((candidate) => candidate.kind === "runtimeExtension" && candidate.exists)) {
return true;
}
const packageDir = path.dirname(packageSummary.path);
return existsSync(path.resolve(packageDir, runtimeBuildSpecifier));
}
function isSourceEntrypoint(specifier) {
return /\.(?:ts|tsx)$/.test(specifier);
}
function runtimeBuildSpecifierFor(specifier) {
const normalized = normalizeEntrypointSpecifier(specifier);
const basename = path.posix.basename(normalized).replace(/\.(?:ts|tsx)$/, ".js");
return `./dist/${basename}`;
}
function normalizeEntrypointSpecifier(specifier) {
const normalized = specifier.replaceAll("\\", "/");
return normalized.startsWith("./") ? normalized : `./${normalized}`;
}
async function findPackageFiles(root, options, depth = 0) {
if (!existsSync(root) || depth > options.maxDepth) {
return [];

View File

@ -342,7 +342,10 @@ export function summarizeIssueClasses(issues) {
}
function issueClassFor(code, options) {
if (["unknown-hook-name", "unknown-registration-name", "package-entrypoint-missing", "sdk-export-missing"].includes(code)) {
if (code === "sdk-export-missing" && options.compatRecord) {
return "compat-gap";
}
if (["unknown-hook-name", "unknown-registration-name", "package-entrypoint-missing"].includes(code)) {
return "live-issue";
}
if (code === "missing-compat-record") {
@ -397,7 +400,7 @@ function severityForClass(code, defaultSeverity, options) {
if (
options.issueClass === "live-issue" &&
["none", "untracked"].includes(options.compatStatus) &&
["unknown-hook-name", "unknown-registration-name", "package-entrypoint-missing", "sdk-export-missing"].includes(code)
["unknown-hook-name", "unknown-registration-name", "package-entrypoint-missing"].includes(code)
) {
return "P0";
}

View File

@ -225,6 +225,10 @@ export function classifyCompatRecordCoverage({ targetOpenClaw, findings, suggest
continue;
}
if (finding.code === "sdk-export-missing") {
continue;
}
suggestions.push({
fixture: finding.fixture,
code: "missing-compat-record",

View File

@ -19,18 +19,18 @@ test("issue ids are stable fingerprints", () => {
test("issue classification separates live breaks from compat and deprecation buckets", () => {
const cases = [
{
name: "untracked SDK alias is a blocking live issue",
name: "untracked SDK alias is a compat gap",
finding: { code: "sdk-export-missing", compatRecord: "plugin-sdk-export-aliases" },
targetOpenClaw: { compatRecordStatuses: {} },
metadata: { severity: "P1" },
expected: { issueClass: "live-issue", compatStatus: "untracked", severity: "P0", live: true },
expected: { issueClass: "compat-gap", compatStatus: "untracked", severity: "P1", live: false },
},
{
name: "active SDK alias compat avoids false P0 escalation",
name: "active SDK alias compat stays a compat row",
finding: { code: "sdk-export-missing", compatRecord: "plugin-sdk-export-aliases" },
targetOpenClaw: { compatRecordStatuses: { "plugin-sdk-export-aliases": "active" } },
metadata: { severity: "P1" },
expected: { issueClass: "live-issue", compatStatus: "active", severity: "P1", live: true },
expected: { issueClass: "compat-gap", compatStatus: "active", severity: "P1", live: false },
},
{
name: "deprecated compat remains warning-class even when used",
@ -112,17 +112,17 @@ test("issue builder applies metadata and class summaries", () => {
assert.deepEqual(
issues.map((issue) => [issue.fixture, issue.code, issue.severity, issue.issueClass, issue.status]),
[
["codex-app-server", "sdk-export-missing", "P0", "live-issue", "blocking"],
["codex-app-server", "sdk-export-missing", "P1", "compat-gap", "open"],
["agentchat", "manifest-unknown-fields", "P2", "upstream-metadata", "open"],
["wecom", "registration-capture-gap", "P2", "inspector-gap", "open"],
],
);
assert.deepEqual(summarizeIssueClasses(issues), {
"compat-gap": 0,
"compat-gap": 1,
"deprecation-warning": 0,
"fixture-regression": 0,
"inspector-gap": 1,
"live-issue": 1,
"live-issue": 0,
"upstream-metadata": 1,
});
});

View File

@ -865,6 +865,57 @@ test("package contract classifier reports advertised npm pack blockers", () => {
assert.equal(globResult.warnings.some((finding) => finding.code.startsWith("package-npm-pack-")), false);
});
test("package contract classifier accepts built runtime entries for source package metadata", () => {
const result = classifyPackageContracts({
fixture: {
id: "fixture",
path: "plugins/fixture",
},
inspection: {
registrations: ["registerTool"],
},
fixtureReport: {
pluginManifests: [{ path: "plugins/fixture/openclaw.plugin.json", version: "1.0.0" }],
package: {
path: "plugins/fixture/package.json",
name: "@openclaw/fixture-plugin",
version: "1.0.0",
dependencies: [],
peerDependencies: ["openclaw"],
optionalDependencies: [],
openclaw: {
compatPluginApi: "^1.0.0",
install: {
npmSpec: "@openclaw/fixture-plugin",
},
release: {
publishToNpm: true,
},
entrypoints: [
{
kind: "extension",
specifier: "./index.ts",
relativePath: "plugins/fixture/index.ts",
exists: false,
requiresBuild: false,
},
{
kind: "runtimeExtension",
specifier: "./dist/index.js",
relativePath: "plugins/fixture/dist/index.js",
exists: true,
requiresBuild: true,
},
],
},
},
},
});
assert.equal(result.warnings.some((finding) => finding.code === "package-entrypoint-missing"), false);
assert.equal(result.decisions.some((decision) => decision.seam === "package-entrypoint"), false);
});
test("target OpenClaw coverage classifier reports missing public surface", () => {
const result = classifyTargetOpenClawCoverage({
fixture: { id: "fixture" },