mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
## 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.
127 lines
4.1 KiB
TypeScript
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);
|