diff --git a/scripts/build-diffs-viewer-runtime.mjs b/scripts/build-diffs-viewer-runtime.mjs index d569e4a8ef2c..ba701dc05091 100644 --- a/scripts/build-diffs-viewer-runtime.mjs +++ b/scripts/build-diffs-viewer-runtime.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node +// Builds browser runtime bundles for the diffs viewer assets. import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -26,6 +27,9 @@ function toPosixPath(value) { return String(value ?? "").replaceAll("\\", "/"); } +/** + * Creates the esbuild plugin that neutralizes Pierre diffs' browser side-effect import. + */ export function createPierreDiffsSideEffectImportPlugin() { return { name: "openclaw-diffs-pierre-side-effect-imports", @@ -55,6 +59,9 @@ export function createPierreDiffsSideEffectImportPlugin() { }; } +/** + * Builds one configured diffs viewer runtime target. + */ export async function buildDiffsViewerRuntime(targetName) { const target = targets[targetName]; if (!target) { diff --git a/scripts/build-stamp.mjs b/scripts/build-stamp.mjs index 57edd449c2b5..43a2eb15f9a0 100644 --- a/scripts/build-stamp.mjs +++ b/scripts/build-stamp.mjs @@ -1,4 +1,6 @@ #!/usr/bin/env node + +// Writes the local build stamp and re-exports build metadata helpers. import process from "node:process"; import { pathToFileURL } from "node:url"; import { writeBuildStamp } from "./lib/local-build-metadata.mjs"; diff --git a/scripts/bundle-a2ui.mjs b/scripts/bundle-a2ui.mjs index ae2ea6439edc..cc4e18eb7c93 100644 --- a/scripts/bundle-a2ui.mjs +++ b/scripts/bundle-a2ui.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node +// Runs bundled asset build hooks for the Canvas A2UI runtime. import { pathToFileURL } from "node:url"; import { runBundledPluginAssetHooks } from "./bundled-plugin-assets.mjs"; diff --git a/scripts/bundled-plugin-assets.mjs b/scripts/bundled-plugin-assets.mjs index 4261a5eac94d..8f88c9ea2bce 100644 --- a/scripts/bundled-plugin-assets.mjs +++ b/scripts/bundled-plugin-assets.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node +// Discovers and runs bundled plugin package asset hooks. import { spawnSync } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; @@ -57,6 +58,9 @@ function resolveAssetCommand(packageJson, phase) { return typeof command === "string" && command.trim() ? command.trim() : null; } +/** + * Reads bundled plugin asset hook commands for a build or copy phase. + */ export async function readBundledPluginAssetHooks(options = {}) { const repoRoot = options.rootDir ?? rootDir; const phase = options.phase; @@ -108,6 +112,9 @@ export async function readBundledPluginAssetHooks(options = {}) { return hooks.toSorted((left, right) => left.pluginDir.localeCompare(right.pluginDir)); } +/** + * Runs bundled plugin asset hook commands for the selected phase/plugins. + */ export async function runBundledPluginAssetHooks(options = {}) { const phase = options.phase; const hooks = await readBundledPluginAssetHooks(options); @@ -131,6 +138,9 @@ export async function runBundledPluginAssetHooks(options = {}) { } } +/** + * Parses `--phase` and repeated `--plugin` flags for asset hook scripts. + */ export function parseBundledPluginAssetArgs(argv) { const args = [...argv]; const plugins = []; diff --git a/scripts/changed-lanes.mjs b/scripts/changed-lanes.mjs index cd0a88ed7255..d3ed38184a15 100644 --- a/scripts/changed-lanes.mjs +++ b/scripts/changed-lanes.mjs @@ -1,3 +1,4 @@ +// Classifies changed files into CI lanes and release metadata scopes. import { execFileSync } from "node:child_process"; import { appendFileSync, existsSync, readFileSync } from "node:fs"; import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs"; @@ -24,6 +25,9 @@ const TEST_PATH_RE = /(?:^|\/)(?:test|__tests__)\/|(?:\.|\/)(?:test|spec|e2e|browser\.test)\.[cm]?[jt]sx?$/u; const PUBLIC_EXTENSION_CONTRACT_RE = /^(?:src\/plugin-sdk\/|src\/plugins\/contracts\/|src\/channels\/plugins\/|scripts\/lib\/plugin-sdk-entrypoints\.json$|scripts\/sync-plugin-sdk-exports\.mjs$|scripts\/generate-plugin-sdk-api-baseline\.ts$)/u; +/** + * Files whose changes are treated as release metadata only. + */ export const RELEASE_METADATA_PATHS = new Set([ "CHANGELOG.md", "apps/android/app/build.gradle.kts", @@ -49,6 +53,9 @@ export const RELEASE_METADATA_PATHS = new Set([ * }} ChangedLaneResult */ +/** + * Normalizes a changed file path into repo-relative POSIX form. + */ export function normalizeChangedPath(inputPath) { return String(inputPath ?? "") .trim() @@ -56,6 +63,9 @@ export function normalizeChangedPath(inputPath) { .replace(/^\.\/+/u, ""); } +/** + * Creates the default changed-lanes result object. + */ export function createEmptyChangedLanes() { return { core: false, @@ -76,6 +86,9 @@ export function createEmptyChangedLanes() { * @param {{ packageJsonChangeKind?: "liveDockerTooling" | "tooling" | null }} [options] * @returns {ChangedLaneResult} */ +/** + * Classifies a list of changed paths into docs, app, extension, core, and tooling lanes. + */ export function detectChangedLanes(changedPaths, options = {}) { const paths = [...new Set(changedPaths.map(normalizeChangedPath).filter(Boolean))] .toSorted((left, right) => left.localeCompare(right)) @@ -217,6 +230,9 @@ export function detectChangedLanes(changedPaths, options = {}) { * @param {{ paths: string[]; base: string; head?: string; staged?: boolean; mergeHeadFirstParent?: boolean }} params * @returns {ChangedLaneResult} */ +/** + * Classifies changed paths with optional package.json before/after contents. + */ export function detectChangedLanesForPaths(params) { const base = params.staged ? params.base @@ -240,6 +256,9 @@ export function detectChangedLanesForPaths(params) { * @param {{ base: string; head?: string; includeWorktree?: boolean; cwd?: string; mergeHeadFirstParent?: boolean }} params * @returns {string[]} */ +/** + * Lists changed paths from git for a base/head comparison. + */ export function listChangedPathsFromGit(params) { const head = params.head ?? "HEAD"; const cwd = params.cwd ?? process.cwd(); @@ -318,6 +337,9 @@ function runGitLsFiles(extraArgs, cwd = process.cwd()) { return output.split("\n").map(normalizeChangedPath).filter(Boolean); } +/** + * Lists staged changed paths for pre-commit checks. + */ export function listStagedChangedPaths(cwd = process.cwd()) { const output = execFileSync("git", ["diff", "--cached", "--name-only", "--diff-filter=ACMRD"], { cwd, @@ -328,6 +350,9 @@ export function listStagedChangedPaths(cwd = process.cwd()) { return output.split("\n").map(normalizeChangedPath).filter(Boolean); } +/** + * Classifies package.json script-only changes from git content. + */ export function classifyPackageJsonChangeFromGit(params) { try { const { before, after } = readPackageJsonBeforeAfter(params); @@ -340,6 +365,9 @@ export function classifyPackageJsonChangeFromGit(params) { } } +/** + * Checks whether package scripts changed only live Docker script entries. + */ export function isLiveDockerPackageScriptOnlyChange(before, after) { const beforePackage = JSON.parse(before); const afterPackage = JSON.parse(after); @@ -354,6 +382,9 @@ export function isLiveDockerPackageScriptOnlyChange(before, after) { ); } +/** + * Checks whether package.json changes are limited to scripts. + */ export function isPackageScriptOnlyChange(before, after) { const beforePackage = JSON.parse(before); const afterPackage = JSON.parse(after); @@ -444,6 +475,9 @@ function stableJson(value) { return JSON.stringify(value); } +/** + * Writes changed-lane booleans to the GitHub Actions output file. + */ export function writeChangedLaneGitHubOutput(result, outputPath = process.env.GITHUB_OUTPUT) { if (!outputPath) { throw new Error("GITHUB_OUTPUT is required"); diff --git a/scripts/check-architecture-smells.mjs b/scripts/check-architecture-smells.mjs index fe4e7ec9bfd8..ada61bb50487 100644 --- a/scripts/check-architecture-smells.mjs +++ b/scripts/check-architecture-smells.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node +// Finds core/plugin architecture boundary smells in TypeScript sources. import { promises as fs } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -10,12 +11,12 @@ import { resolveRepoSpecifier, writeLine, } from "./lib/guard-inventory-utils.mjs"; +import { mapWithConcurrency } from "./lib/source-file-scan-cache.mjs"; import { collectTypeScriptFilesFromRoots, resolveSourceRoots, runAsScript, } from "./lib/ts-guard-utils.mjs"; -import { mapWithConcurrency } from "./lib/source-file-scan-cache.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const scanRoots = resolveSourceRoots(repoRoot, ["src/plugin-sdk", "src/plugins/runtime"]); @@ -165,23 +166,22 @@ function scanRuntimeServiceLocatorSmells(source, filePath) { return entries; } +/** + * Collects architecture smell findings from the configured source roots. + */ export async function collectArchitectureSmells() { if (!architectureSmellsPromise) { architectureSmellsPromise = (async () => { const files = (await collectTypeScriptFilesFromRoots(scanRoots)).toSorted((left, right) => normalizeRepoPath(repoRoot, left).localeCompare(normalizeRepoPath(repoRoot, right)), ); - const entriesByFile = await mapWithConcurrency( - files, - undefined, - async (filePath) => { - const source = await fs.readFile(filePath, "utf8"); - const entries = scanPluginSdkExtensionFacadeSmells(source, filePath); - entries.push(...scanRuntimeTypeImplementationSmells(source, filePath)); - entries.push(...scanRuntimeServiceLocatorSmells(source, filePath)); - return entries; - }, - ); + const entriesByFile = await mapWithConcurrency(files, undefined, async (filePath) => { + const source = await fs.readFile(filePath, "utf8"); + const entries = scanPluginSdkExtensionFacadeSmells(source, filePath); + entries.push(...scanRuntimeTypeImplementationSmells(source, filePath)); + entries.push(...scanRuntimeServiceLocatorSmells(source, filePath)); + return entries; + }); return entriesByFile.flat().toSorted(compareEntries); })(); try { @@ -235,6 +235,9 @@ async function runArchitectureSmellsCheck(argv, io) { return 0; } +/** + * Runs the architecture smell check and writes human/JSON output. + */ export async function main(argv, io) { return await runArchitectureSmellsCheck(argv, io); } diff --git a/scripts/check-changelog-attributions.mjs b/scripts/check-changelog-attributions.mjs index 00e6c6760276..85dac49534b9 100644 --- a/scripts/check-changelog-attributions.mjs +++ b/scripts/check-changelog-attributions.mjs @@ -1,9 +1,13 @@ #!/usr/bin/env node +// Rejects changelog thanks entries that credit bots or internal handles. import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; +/** + * Exact handles that changelog thanks entries must not credit. + */ export const FORBIDDEN_CHANGELOG_THANKS_HANDLES = [ "codex", "openclaw", @@ -13,20 +17,38 @@ export const FORBIDDEN_CHANGELOG_THANKS_HANDLES = [ "clawsweeper[bot]", "openclaw-clawsweeper[bot]", ]; +/** + * Handle prefixes that identify forbidden changelog thanks credits. + */ export const FORBIDDEN_CHANGELOG_THANKS_HANDLE_PREFIXES = ["app/"]; +/** + * Handle suffixes that identify forbidden changelog thanks credits. + */ export const FORBIDDEN_CHANGELOG_THANKS_HANDLE_SUFFIXES = ["[bot]"]; +/** + * Handles that require an explicit human credit instead. + */ export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLES = [ "clawsweeper", "openclaw-clawsweeper", "clawsweeper[bot]", "openclaw-clawsweeper[bot]", ]; +/** + * Handle prefixes that require explicit human credit instead. + */ export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_PREFIXES = ["app/"]; +/** + * Handle suffixes that require explicit human credit instead. + */ export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_SUFFIXES = ["[bot]"]; const THANKS_PATTERN = /\bThanks\b/iu; const THANKED_HANDLE_PATTERN = /@([-_/A-Za-z0-9]+(?:\[bot\])?)/giu; +/** + * Reports whether a handle is forbidden in changelog thanks text. + */ export function isForbiddenChangelogThanksHandle(handle, options = {}) { const { strictBotHandle = false } = options; const normalized = handle.toLowerCase(); @@ -48,6 +70,9 @@ export function isForbiddenChangelogThanksHandle(handle, options = {}) { return false; } +/** + * Reports whether a handle needs a separate human credit. + */ export function requiresExplicitHumanChangelogThanks(handle) { const normalized = handle.toLowerCase(); if (normalized === "" || normalized === "null") { @@ -64,6 +89,9 @@ export function requiresExplicitHumanChangelogThanks(handle) { ); } +/** + * Finds changelog lines that thank forbidden handles. + */ export function findForbiddenChangelogThanks(content) { return content .split(/\r?\n/u) @@ -82,6 +110,9 @@ export function findForbiddenChangelogThanks(content) { .filter(Boolean); } +/** + * Runs the changelog attribution check. + */ export async function main(argv = process.argv.slice(2)) { if (argv[0] === "--is-forbidden-handle") { process.exitCode = isForbiddenChangelogThanksHandle(argv[1] ?? "", { diff --git a/scripts/check-channel-agnostic-boundaries.mjs b/scripts/check-channel-agnostic-boundaries.mjs index c42f0c83a6eb..82d9a87b4878 100644 --- a/scripts/check-channel-agnostic-boundaries.mjs +++ b/scripts/check-channel-agnostic-boundaries.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node +// Checks channel-agnostic core surfaces for channel-specific coupling. import { promises as fs } from "node:fs"; import path from "node:path"; import ts from "typescript"; @@ -117,6 +118,9 @@ function isModuleSpecifierStringNode(node) { ); } +/** + * Finds channel-specific references inside channel-agnostic protected sources. + */ export function findChannelAgnosticBoundaryViolations( content, fileName = "source.ts", @@ -236,6 +240,9 @@ export function findChannelAgnosticBoundaryViolations( return violations; } +/** + * Finds reverse dependencies from channel core into plugin/runtime surfaces. + */ export function findChannelCoreReverseDependencyViolations(content, fileName = "source.ts") { return findChannelAgnosticBoundaryViolations(content, fileName, { checkModuleSpecifiers: true, @@ -246,6 +253,9 @@ export function findChannelCoreReverseDependencyViolations(content, fileName = " }); } +/** + * Finds user-facing channel names in ACP-owned text sources. + */ export function findAcpUserFacingChannelNameViolations(content, fileName = "source.ts") { const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true); const violations = []; @@ -265,6 +275,9 @@ export function findAcpUserFacingChannelNameViolations(content, fileName = "sour return violations; } +/** + * Finds raw system mark literals where shared constants should be used. + */ export function findSystemMarkLiteralViolations(content, fileName = "source.ts") { const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true); const violations = []; @@ -307,6 +320,9 @@ const boundaryRuleSets = [ }, ]; +/** + * Runs all channel-agnostic boundary checks. + */ export async function main() { const violations = []; for (const ruleSet of boundaryRuleSets) { diff --git a/scripts/check-cli-bootstrap-imports.mjs b/scripts/check-cli-bootstrap-imports.mjs index c888e218b316..0609f3a85b60 100644 --- a/scripts/check-cli-bootstrap-imports.mjs +++ b/scripts/check-cli-bootstrap-imports.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node +// Checks CLI bootstrap chunks for forbidden eager imports and size regressions. import fs from "node:fs"; import module from "node:module"; import path from "node:path"; @@ -57,6 +58,9 @@ function resolveRelativeImport(importer, specifier, fsImpl = fs) { }); } +/** + * Lists static import/export specifiers from a JavaScript source string. + */ export function listStaticImportSpecifiers(source) { return [...source.matchAll(STATIC_IMPORT_RE)].map((match) => match.groups?.specifier ?? ""); } @@ -110,6 +114,9 @@ function walkStaticImportGraph(params) { return errors; } +/** + * Collects forbidden external import errors for CLI bootstrap entrypoints. + */ export function collectCliBootstrapExternalImportErrors(params = {}) { const rootDir = params.rootDir ?? process.cwd(); const entrypoints = params.entrypoints ?? DEFAULT_ENTRYPOINTS; @@ -152,6 +159,9 @@ function listJsFiles(dirPath, fsImpl = fs) { return files; } +/** + * Collects gateway-run chunk budget errors from built CLI output. + */ export function collectGatewayRunChunkBudgetErrors(params = {}) { const rootDir = params.rootDir ?? process.cwd(); const fsImpl = params.fs ?? fs; @@ -227,6 +237,9 @@ export function collectGatewayRunChunkBudgetErrors(params = {}) { return errors.toSorted((left, right) => left.localeCompare(right)); } +/** + * Runs the CLI bootstrap import and chunk-budget checks. + */ export function checkCliBootstrapExternalImports(params = {}) { const errors = [ ...collectCliBootstrapExternalImportErrors(params), diff --git a/scripts/check-cli-startup-memory.mjs b/scripts/check-cli-startup-memory.mjs index f0c9339003a8..f9c847e47e54 100644 --- a/scripts/check-cli-startup-memory.mjs +++ b/scripts/check-cli-startup-memory.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node +// Measures CLI startup memory with an isolated home and RSS hook. import { spawnSync as defaultSpawnSync } from "node:child_process"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; @@ -358,6 +359,9 @@ function runStartupMemoryCheck(argv = process.argv.slice(2), params = {}) { return { skipped: false, results }; } +/** + * Test-only access to pure startup memory helper functions. + */ export const testing = { cases, parseArgs,