openclaw/scripts/check-plugin-extension-import-boundary.mts
Peter Steinberger 799ddbd35d
refactor(comments): deslop production narration
## What Problem This Solves

Production code still carries duplicated narration over function names, types, branches and CSS selectors. Some of that prose has drifted: Zalo polling is described as development-only even though it is the default production route, and a joined hook helper is called fire-and-forget.

## User Impact

No user-visible behavior changes. Runtime logic, templates, CSS declarations, configuration, persisted state, wire formats, public API documentation, licenses and lint-suppression reasons remain intact.

## Why This Change Was Made

This maintainer-requested cleanup removes redundant internal helper/registrar summaries, repeated section labels, and obsolete inline font-size history. Existing declarations and shared owners already express these facts; no new abstraction is needed. Comments explaining authority, lifecycle, ordering, cleanup, platform constraints, dependencies and public contracts stay.

The measured reduction is 696 net production/tooling lines: 604 standalone comment lines and 92 adjacent blank lines, plus 59 inline comment removals without net line savings. No tests or generated files changed. This is a bounded contextual sweep, not a claim of exhaustive repository coverage; the local census records exact findings, retained candidates, and unread files. Filename-header cleanup from #161768 is excluded.

## Evidence

Independent review completed; all accepted documentation findings were addressed by restoring base comments. The remaining changed files are byte-identical to the reviewed and remotely frozen candidate.

Blacksmith Testbox validation:
- Parser comparison: identical non-comment TypeScript tokens and CSS structure.
- Both import-cycle checks: 0 cycles.
- Focused existing tests: 40 Vitest shards passed (521.71 seconds).
- Plugin contracts: 48 files / 1,153 tests passed.
- Plugin, source-to-extension, and SDK/package import-boundary checks passed.
- Feishu asset hook check: no build hooks; no plugin browser/control-UI source changed.

SDK API comparison passed with no API changes. The full changed-file gate passed remotely. The lowered-threshold duplicate census (12 lines / 80 tokens) completed; its raw 155 records include deliberate probe/fixture matches and are not claimed as removable production code. No tests were added or changed.

Public JSDoc was audited independently: the SDK API comparison strips comments, while shipped declarations can preserve them, so API-shape equality alone would not prove documentation preservation.


### Inherited hosted CI failure

Exact-head [CI run 36815106181](https://github.com/openclaw/openclaw/actions/runs/36815106181) tested `ff26a4c05d3b41d25477df41cb94010c6cac5cb0` merged with main `75d1f82c18`. The only failing test job was `checks-node-compact-small-19`: `test/helpers/openclaw-test-instance.acquisition.test.ts:33`, “keeps an absent Gateway unreachable while retaining its port claims,” expected `free` but received `busy`. The other failure is the aggregate CI gate. This attempt has 84 successful jobs, 16 skipped jobs, and 88 collateral cancellations; cancelled coverage is not counted as passing.

The identical assertion and error were independently verified in [job 110052175838](https://github.com/openclaw/openclaw/actions/runs/36763489663/job/110052175838), the latest attempt (1) for unrelated PR #162070's final head `d07a6981d4`. The acquisition test, instance helper, cleanup wrapper, isolated-state writer, port allocator, claim owner, claim-lock owner and TCP probe are byte-identical between that head and this PR. No changed file participates in the failing acquisition/probe path. The failure precedes Gateway startup; the logs do not identify the competing listener, so no root-cause repair is claimed.

Landing uses the maintainer-authorized inherited-failure exception pinned to this exact head, backed by the passing remote gates above. The fixture defect remains with the main-CI coordinator. No workflow rerun, test weakening, timeout increase, or source repush was used to obtain green. GitHub's GraphQL writer rejected auto-merge because its quota was exhausted; the request was reconciled as absent before selecting the supported REST squash path.
2026-09-30 22:28:36 -07:00

127 lines
4.1 KiB
TypeScript

#!/usr/bin/env node
// Inventories core plugin imports that cross into bundled extension files.
import { existsSync } from "node:fs";
import path from "node:path";
import {
compareEntries,
createExtensionImportBoundaryChecker,
} from "./lib/extension-import-boundary-checker.mts";
import {
formatGroupedInventoryHuman,
resolveRepoSpecifier,
writeLine,
} from "./lib/guard-inventory-utils.mjs";
import { resolveRepoRoot } from "./lib/repo-root.mjs";
import { runAsScript } from "./lib/ts-guard-utils.mts";
const repoRoot = resolveRepoRoot(import.meta.url);
const AUTHORED_MODULE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
const RETIRED_WEB_SEARCH_CORE_MODULES = [
"src/agents/tools/web-search-plugin-factory",
"src/plugins/bundled-web-search-registry",
"src/plugins/web-search-providers",
] as const;
type PluginExtensionInventoryEntry = {
file: string;
line: number;
kind: string;
specifier: string;
resolvedPath: string | null;
reason: string;
};
function classifyResolvedExtensionReason(kind: string, resolvedPath: string | null) {
const verb =
kind === "export"
? "re-exports"
: kind === "dynamic-import"
? "dynamically imports"
: "imports";
if (/^extensions\/[^/]+\/src\//.test(resolvedPath ?? "")) {
return `${verb} extension implementation from src/plugins`;
}
if (/^extensions\/[^/]+\/index\.[^/]+$/.test(resolvedPath ?? "")) {
return `${verb} extension entrypoint from src/plugins`;
}
return `${verb} extension-owned file from src/plugins`;
}
const boundaryChecker = createExtensionImportBoundaryChecker({
roots: ["src/plugins"],
shouldSkipFile(relativeFile) {
return (
relativeFile.startsWith("src/plugins/contracts/") ||
/^src\/plugins\/runtime\/runtime-[^/]+-contract\.[cm]?[jt]s$/u.test(relativeFile)
);
},
collectEntries({ filePath, relativeFile, references }) {
return references.map(({ kind, line, specifier }) => {
const resolvedPath = resolveRepoSpecifier(repoRoot, specifier, filePath);
return {
file: relativeFile,
line,
kind,
specifier,
resolvedPath,
reason: classifyResolvedExtensionReason(kind, resolvedPath),
};
});
},
compareEntries,
});
/** Rejects retired core registries whose ownership now comes from plugin manifests. */
export function collectRetiredWebSearchCorePathEntries(
rootDir = repoRoot,
): PluginExtensionInventoryEntry[] {
return RETIRED_WEB_SEARCH_CORE_MODULES.flatMap((modulePath) =>
AUTHORED_MODULE_EXTENSIONS.map((extension) => `${modulePath}${extension}`),
)
.filter((relativeFile) => existsSync(path.join(rootDir, relativeFile)))
.map((relativeFile) => ({
file: relativeFile,
line: 1,
kind: "retired-path",
specifier: relativeFile,
resolvedPath: relativeFile,
reason: "restores retired core web-search registry or factory ownership",
}));
}
/** Inventory of src/plugins extension imports and retired core web-search ownership paths. */
async function collectPluginExtensionImportBoundaryInventory() {
return [
...(await boundaryChecker.collectInventory()),
...collectRetiredWebSearchCorePathEntries(),
].toSorted(compareEntries);
}
const ruleText =
"Rule: src/plugins/** must not import bundled plugin files or restore retired web-search registries";
const formatInventoryHuman = (inventory: PluginExtensionInventoryEntry[]) =>
formatGroupedInventoryHuman(
{
rule: ruleText,
cleanMessage: "No plugin import boundary violations found.",
inventoryTitle: "Plugin extension import boundary inventory:",
},
inventory,
);
async function runPluginExtensionImportBoundaryCheck(): Promise<0 | 1> {
const actual = await collectPluginExtensionImportBoundaryInventory();
writeLine(process.stdout, formatInventoryHuman(actual));
if (actual.length === 0) {
return 0;
}
writeLine(process.stderr, `${ruleText} violations found (${actual.length}).`);
return 1;
}
async function main(): Promise<void> {
process.exitCode = await runPluginExtensionImportBoundaryCheck();
}
runAsScript(import.meta.url, main);