"""Check ComfyUI host compatibility surfaces used by ComfyUI-Doctor. This script is intentionally lightweight. It validates that the local `reference/` checkouts still expose the host APIs Doctor depends on; it does not replace full host E2E validation. """ from __future__ import annotations import argparse from collections.abc import Iterable, Sequence from dataclasses import dataclass from pathlib import Path @dataclass(frozen=True) class SurfaceCheck: repo: str file: str label: str required_patterns: tuple[str, ...] forbidden_patterns: tuple[str, ...] = () source_revision: str = "" applicable_lanes: tuple[str, ...] = () note: str = "" @dataclass(frozen=True) class FrontendRuntimeLane: id: str label: str version: str source_repo: str source_file: str source_revision: str setting_change_telemetry: bool @dataclass(frozen=True) class CheckResult: check: SurfaceCheck ok: bool missing_file: bool = False missing_patterns: tuple[str, ...] = () present_forbidden_patterns: tuple[str, ...] = () COMFYUI_REVISION = "dd79c643a95402136a75a28f6187d843bcf457ed" # pragma: allowlist secret FRONTEND_REVISION = "bdc0345da4610f16b4102f7e4628905b09b165ac" # pragma: allowlist secret DESKTOP_REVISION = "e2d964b7456cea8423c7b9d3371c612313c06baa" # pragma: allowlist secret FRONTEND_RUNTIME_LANES: tuple[FrontendRuntimeLane, ...] = ( FrontendRuntimeLane( id="desktop-0.9.4", label="Desktop 0.9.4 bundle", version="1.43.18", source_repo="desktop", source_file="package.json", source_revision=DESKTOP_REVISION, setting_change_telemetry=False, ), FrontendRuntimeLane( id="core-pin-1.48.7", label="ComfyUI package pin", version="1.48.7", source_repo="ComfyUI", source_file="requirements.txt", source_revision=COMFYUI_REVISION, setting_change_telemetry=True, ), FrontendRuntimeLane( id="standalone-1.50.3+", label="standalone frontend source", version="1.50.3+", source_repo="ComfyUI_frontend", source_file="package.json", source_revision=FRONTEND_REVISION, setting_change_telemetry=True, ), ) CHECKS: tuple[SurfaceCheck, ...] = ( SurfaceCheck( repo="ComfyUI", file="main.py", label="custom node prestartup loading", required_patterns=("prestartup_script.py", "execute_prestartup_script"), ), SurfaceCheck( repo="ComfyUI", file="nodes.py", label="WEB_DIRECTORY registration", required_patterns=("EXTENSION_WEB_DIRS", "WEB_DIRECTORY"), ), SurfaceCheck( repo="ComfyUI", file="server.py", label="extension static routes", required_patterns=('@routes.get("/extensions")', "EXTENSION_WEB_DIRS", "web.static('/extensions/'"), ), SurfaceCheck( repo="ComfyUI", file="server.py", label="PromptServer route registry", required_patterns=("class PromptServer", "self.routes"), ), SurfaceCheck( repo="ComfyUI", file="server.py", label="host /api route duplication", required_patterns=( "Prefix every route with /api", '"/api" + route.path', "self.app.add_routes(api_routes)", "self.app.add_routes(self.routes)", ), ), SurfaceCheck( repo="ComfyUI", file="server.py", label="feature-flag websocket exchange", required_patterns=( 'data.get("type") == "feature_flags"', 'self.sockets_metadata[sid]["feature_flags"]', "feature_flags.get_server_features()", '@routes.get("/features")', ), ), SurfaceCheck( repo="ComfyUI", file="server.py", label="system_stats current multi-device package versions", required_patterns=( '@routes.get("/system_stats")', "get_all_torch_devices", "device_entries", "comfy_package_versions", "FrontendManager.get_comfy_package_versions()", ), ), SurfaceCheck( repo="ComfyUI", file="server.py", label="system_stats deploy environment", required_patterns=( "get_deploy_environment", '"deploy_environment": get_deploy_environment()', ), ), SurfaceCheck( repo="ComfyUI", file="server.py", label="jobs namespace cancel endpoints", required_patterns=( 'cancel_job(', "CANCEL_RUNNING", "CANCEL_PENDING", "interrupt_if_running", '@routes.post("/api/jobs/{job_id}/cancel")', '@routes.post("/api/jobs/cancel")', ), ), SurfaceCheck( repo="ComfyUI", file="server.py", label="prompt usage source pass-through", required_patterns=( '"comfy_usage_source" not in extra_data', 'request.headers.get("Comfy-Usage-Source")', 'extra_data["comfy_usage_source"] = usage_source', ), note=( "Tracks host source-attribution pass-through only; Doctor must not " "attach secrets or private prompt data to usage-source metadata." ), ), SurfaceCheck( repo="ComfyUI", file="execution.py", label="execution_error websocket payload", required_patterns=( '"execution_error"', '"node_id"', '"node_type"', '"traceback"', '"current_inputs"', '"current_outputs"', ), ), SurfaceCheck( repo="ComfyUI", file="execution.py", label="execution usage source hidden input", required_patterns=( "io.Hidden.comfy_usage_source.name in hidden", "hidden_inputs_v3[io.Hidden.comfy_usage_source]", 'extra_data.get("comfy_usage_source", None)', ), ), SurfaceCheck( repo="ComfyUI", file="execution.py", label="executed output asset enrichment tolerance", required_patterns=( "enrich_output_with_assets(output_ui)", '"executed"', '"output": output_ui', ), note=( "Doctor currently treats executed output as pass-through host data; " "this guard records upstream enrichment without requiring runtime parsing." ), ), SurfaceCheck( repo="ComfyUI", file="execution.py", label="prompt queue running job interrupt hook", required_patterns=( "def interrupt_if_running", "currently_running", "interrupt_processing", ), ), SurfaceCheck( repo="ComfyUI", file="comfy_execution/jobs.py", label="jobs cancel classification helpers", required_patterns=( "CANCEL_RUNNING", "CANCEL_PENDING", "CANCEL_TERMINAL", "CANCEL_UNKNOWN", "def cancel_job", "def classify_job_for_cancel", ), ), SurfaceCheck( repo="ComfyUI", file="comfy_execution/progress.py", label="progress_state lineage payload", required_patterns=( '"progress_state"', '"display_node_id"', '"parent_node_id"', '"real_node_id"', "supports_preview_metadata", ), ), SurfaceCheck( repo="ComfyUI", file="folder_paths.py", label="current model folder anchors", required_patterns=( '"geometry_estimation"', '"detection"', "get_system_user_directory", "get_public_user_directory", ), ), SurfaceCheck( repo="ComfyUI", file="folder_paths.py", label="current expanded model folder anchors", required_patterns=( '"diffusion_models"', '"text_encoders"', '"clip_vision"', '"style_models"', '"photomaker"', '"classifiers"', '"model_patches"', '"audio_encoders"', '"background_removal"', '"frame_interpolation"', '"optical_flow"', ), ), SurfaceCheck( repo="ComfyUI", file="comfy_api/feature_flags.py", label="server feature flags", required_patterns=( '"supports_preview_metadata": True', '"extension": {"manager": {"supports_v4": True}}', '"node_replacements": True', '"enable_telemetry":', "def get_server_features", ), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/types/extensionTypes.ts", label="extensionManager execution error state", required_patterns=( "lastNodeErrors", "lastExecutionError", "ExecutionErrorWsMessage", "NodeError", ), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/scripts/api.ts", label="frontend queue prompt usage source", required_patterns=( "async queuePrompt(", "extra_data:", "comfy_usage_source: 'comfyui-frontend'", ), note="Tracks frontend prompt source attribution; Doctor does not queue prompts in this item.", ), SurfaceCheck( repo="ComfyUI_frontend", file="src/scripts/api.ts", label="frontend jobs cancel API", required_patterns=( "async cancelJob(", "/cancel", "async cancelJobs(", "'/jobs/cancel'", "job_ids: jobIds", ), note="Tracks host prompt/job cancellation API adoption; Doctor does not call these endpoints in this item.", ), SurfaceCheck( repo="ComfyUI_frontend", file="src/types/extensionTypes.ts", label="extensionManager settings/sidebar API", required_patterns=("registerSidebarTab", "setting:", "get: ", "set: "), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/stores/workspaceStore.ts", label="deprecated sidebar wrapper fallback", required_patterns=( "const sidebarTab = computed(() => useSidebarTabStore())", "Use `sidebarTab.registerSidebarTab` instead", "sidebarTab.value.registerSidebarTab(tab)", "Use `sidebarTab.unregisterSidebarTab` instead", "sidebarTab.value.unregisterSidebarTab(id)", "Use `sidebarTab.sidebarTabs` instead", ), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/stores/workspace/sidebarTabStore.ts", label="current sidebarTab store API", required_patterns=( "defineStore('sidebarTab'", "const sidebarTabs = ref([])", "const activeSidebarTabId = ref(null)", "const registerSidebarTab =", "const unregisterSidebarTab =", "toggleSidebarTab", ), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/schemas/apiSchema.ts", label="frontend execution_error schema", required_patterns=( "zExecutionErrorWsMessage", "node_id:", "node_type:", "traceback:", "current_inputs:", "current_outputs:", ), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/scripts/app.ts", label="frontend rootGraph API", required_patterns=("rootGraph", "lastExecutionError"), ), SurfaceCheck( repo="desktop", file="package.json", label="Desktop current bundled host baseline", required_patterns=( '"version": "0.9.4"', '"comfyUI"', '"version": "0.22.3"', ), ), SurfaceCheck( repo="desktop", file="src/main-process/comfyServer.ts", label="Desktop base/user/input/output directories", required_patterns=( "userDirectoryPath", "inputDirectoryPath", "outputDirectoryPath", "path.join(this.basePath, 'user')", "'user-directory': this.userDirectoryPath", "'input-directory': this.inputDirectoryPath", "'output-directory': this.outputDirectoryPath", "'front-end-root': this.webRootPath", "'base-directory': this.basePath", "'database-url': this.databaseUrl", "'extra-model-paths-config': ComfyServerConfig.configPath", ), ), SurfaceCheck( repo="desktop", file="src/virtualEnvironment.ts", label="Desktop managed .venv layout", required_patterns=("this.venvPath = path.join(basePath, '.venv')", "Scripts', 'python.exe'", "bin', 'python'"), ), SurfaceCheck( repo="desktop", file="src/config/comfyConfigManager.ts", label="Desktop valid basePath shape", required_patterns=( "DEFAULT_DIRECTORIES", "'custom_nodes'", "'input'", "'output'", "['user', ['default']]", "'models'", "const requiredSubdirs = ['models', 'input', 'user', 'output', 'custom_nodes']", ), ), SurfaceCheck( repo="desktop", file="src/config/comfySettings.ts", label="Desktop settings path", required_patterns=("path.join(this.#basePath, 'user', 'default', 'comfy.settings.json')",), ), SurfaceCheck( repo="desktop", file="package.json", label="frontend runtime lane: Desktop bundle", required_patterns=('"frontend"', '"version": "1.43.18"'), source_revision=DESKTOP_REVISION, applicable_lanes=("desktop-0.9.4",), note="Setting-change telemetry from frontend v1.47.7 is absent in this bundled lane.", ), SurfaceCheck( repo="ComfyUI", file="requirements.txt", label="frontend runtime lane: ComfyUI package pin", required_patterns=("comfyui-frontend-package==1.48.7",), source_revision=COMFYUI_REVISION, applicable_lanes=("core-pin-1.48.7",), note="The package pin includes the setting-change telemetry contract; host activation remains separately gated.", ), SurfaceCheck( repo="ComfyUI_frontend", file="package.json", label="frontend runtime lane: standalone source", required_patterns=('"version": "1.50.3"',), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), note="Current standalone source contains the setting-change telemetry contract.", ), SurfaceCheck( repo="ComfyUI", file="README.md", label="minimum supported PyTorch version", required_patterns=("torch 2.7 is minimally supported",), source_revision=COMFYUI_REVISION, applicable_lanes=("comfyui-core-v0.31.0+",), note="Source policy evidence only; Doctor's runtime diagnostic is aligned separately in R48.", ), SurfaceCheck( repo="ComfyUI_frontend", file="src/lib/litegraph/src/LiteGraphGlobal.ts", label="named widget restore global default", required_patterns=("namedValuesRestore: boolean = false",), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), note="Doctor retains positional-first diagnostics while the host default remains false.", ), SurfaceCheck( repo="ComfyUI_frontend", file="src/platform/settings/constants/coreSettings.ts", label="named widget restore setting boundary", required_patterns=( "id: 'Comfy.Workflow.NamedValuesRestore',\n" " name: 'Restore widget values by name',\n" " type: 'boolean',\n" " defaultValue: false,\n" " experimental: true", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/lib/litegraph/src/LGraphNode.ts", label="named widget restore authority branch", required_patterns=( "if (namedValues && LiteGraph.namedValuesRestore)", "} else if (info.widgets_values) {", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), note="A future default/authority change requires an explicit Doctor precedence review.", ), SurfaceCheck( repo="ComfyUI_frontend", file="src/core/graph/subgraph/resolveConcretePromotedWidget.ts", label="promoted widget concrete source resolution", required_patterns=( "export function resolveConcretePromotedWidget(", "resolved: { node: sourceNode, nodePath, widget: sourceWidget }", "export function buildPromotedSourceExecutionId(", "createNodeExecutionId([...hostNodeIds, ...nodePath])", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), note="Public source evidence for R49; Doctor production code must not import frontend internals.", ), SurfaceCheck( repo="ComfyUI_frontend", file="src/platform/missingMedia/missingMediaScan.ts", label="missing media upload spec classification", required_patterns=( "if (!spec || !isComboInputSpec(spec)) return undefined", "if (spec.video_upload) return 'video'", "if (spec.image_upload || spec.animated_image_upload) return 'image'", "if (spec.audio_upload) return 'audio'", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), note="Private host behavior is observed as source evidence only; R49 remains public-contract bounded.", ), SurfaceCheck( repo="ComfyUI", file="comfy_api/feature_flags.py", label="model type tag feature flag", required_patterns=('"supports_model_type_tags": True', "_CORE_FEATURE_FLAGS"), source_revision=COMFYUI_REVISION, applicable_lanes=("comfyui-core-v0.31.0+",), ), SurfaceCheck( repo="ComfyUI", file="comfy/cli_args.py", label="models directory CLI override", required_patterns=( 'parser.add_argument("--models-directory"', "is_valid_directory", "Overrides the models folder in --base-directory", ), source_revision=COMFYUI_REVISION, applicable_lanes=("comfyui-core-v0.31.0+",), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/platform/remote/comfyui/jobs/fetchJobs.ts", label="cached history query without client id", required_patterns=( "async function fetchJobsRaw(", "const url = `/jobs?status=${statusParam}&limit=${maxItems}&offset=${offset}`", "export async function fetchHistory(", "export async function fetchHistoryPage(", "['completed', 'failed', 'cancelled']", ), forbidden_patterns=("client_id", "clientId"), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/platform/settings/settingStore.ts", label="setting telemetry defaults", required_patterns=( "const isVisible = setting.type !== 'hidden'", "const trackChanges = telemetry?.trackChanges ?? isVisible", "const includeValues = telemetry?.includeValues ?? isVisible", "if (event) useTelemetry()?.trackSettingChanged(event)", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/platform/settings/types.ts", label="setting telemetry opt-out type", required_patterns=( "type SettingTelemetryOptions", "trackChanges: false", "includeValues?: never", "telemetry?: SettingTelemetryOptions", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/platform/telemetry/initTelemetry.ts", label="cloud telemetry initialization gate", required_patterns=( "const IS_CLOUD_BUILD = __DISTRIBUTION__ === 'cloud'", "if (!IS_CLOUD_BUILD) return", "setTelemetryRegistry(registry)", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/platform/telemetry/initHostTelemetry.ts", label="host telemetry initialization gate", required_patterns=( "const ENABLE_TELEMETRY_FEATURE = 'enable_telemetry'", "remoteConfig.value.enable_telemetry === true", "if (!isHostTelemetryEnabled()) return", "if (!window.__comfyDesktop2?.Telemetry) return", "setTelemetryRegistry(registry)", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/lib/litegraph/src/subgraph/SubgraphNode.ts", label="real SubgraphNode public shape", required_patterns=( "export class SubgraphNode extends LGraphNode implements BaseLGraph", "override readonly isVirtualNode = true as const", "override isSubgraphNode()", "readonly subgraph: Subgraph", "override getInnerNodes(", ), source_revision=FRONTEND_REVISION, applicable_lanes=( "desktop-0.9.4", "core-pin-1.48.7", "standalone-1.50.3+", ), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/core/graph/subgraph/liftNodeErrorsToBoundary.ts", label="boundary error source provenance", required_patterns=( "input_name: surface.hostInputName", "source_execution_id: executionId", "source_input_name: inputName", "export function liftNodeErrorsToBoundary(", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/stores/executionErrorStore.ts", label="surfaced error derivation", required_patterns=( "const surfacedNodeErrors = computed(() =>", "lastNodeErrors.value && app.isGraphReady", "liftNodeErrorsToBoundary(app.rootGraph, lastNodeErrors.value)", ": lastNodeErrors.value", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), note="This is private host evidence; Doctor production code may use only public raw error state.", ), SurfaceCheck( repo="ComfyUI_frontend", file="browser_tests/assets/missing/missing_model_nested_promoted_widget.json", label="nested promoted missing-model serialization", required_patterns=( '"definitions"', '"subgraphs"', '"ckpt_name"', '"widgets_values"', ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), note="The fixture is read as inert text and is never executed.", ), SurfaceCheck( repo="ComfyUI_frontend", file="src/lib/litegraph/src/LGraphNode.ts", label="dual positional and named widget serialization", required_patterns=( "o.widgets_values = []", "o.widgets_values_named = {}", "o.widgets_values[i] = serialisedVal", "o.widgets_values_named[widget.name] = serialisedVal", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), note="Current workflows may carry both positional and widget-name keyed values.", ), SurfaceCheck( repo="ComfyUI_frontend", file="src/utils/executionErrorUtil.ts", label="partner node validation classification", required_patterns=( "NODE_LEVEL_VALIDATION_ERROR_TYPES", "'PARTNER_NODE_DISABLED'", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), ), SurfaceCheck( repo="ComfyUI_frontend", file="src/scripts/app.ts", label="partner policy 403 handling", required_patterns=( "const hasPromptNodeErrors =", "error.status === 403", "!hasPromptNodeErrors", ), source_revision=FRONTEND_REVISION, applicable_lanes=("standalone-1.50.3+",), ), SurfaceCheck( repo="ComfyUI", file="folder_paths.py", label="datasets folder registration", required_patterns=('folder_names_and_paths["datasets"] =',), source_revision=COMFYUI_REVISION, applicable_lanes=("comfyui-core-v0.31.0+",), ), SurfaceCheck( repo="ComfyUI", file="comfy_extras/nodes_dataset.py", label="first-party dataset node registrations", required_patterns=( 'node_id="LoadImageDataSetFromFolder"', 'node_id="LoadImageTextDataSetFromFolder"', 'node_id="LoadVideoDataSetFromFolder"', 'node_id="LoadVideoTextDataSetFromFolder"', 'node_id="LoadTrainingDataset"', ), source_revision=COMFYUI_REVISION, applicable_lanes=("comfyui-core-v0.31.0+",), ), SurfaceCheck( repo="ComfyUI", file="comfy/cli_args.py", label="DETAIL logging CLI contract", required_patterns=( "LOG_LEVELS = ('DEBUG', 'DETAIL', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')", "class VerboseAction", "def get_console_log_level", "def get_file_log_outputs", ), source_revision=COMFYUI_REVISION, applicable_lanes=("comfyui-core-v0.31.0+",), note="Observed host logging surface only; Doctor does not ingest host log files.", ), SurfaceCheck( repo="ComfyUI", file="main.py", label="DETAIL file logging bootstrap", required_patterns=( "file_log_outputs = get_file_log_outputs(args.verbose)", "file_outputs=file_log_outputs", ), forbidden_patterns=("file_log_outputs = [('DETAIL', 'comfyui_detail.log')",), source_revision=COMFYUI_REVISION, applicable_lanes=("comfyui-core-v0.31.0+",), note="Observed host logging surface only; Doctor does not ingest host log files.", ), SurfaceCheck( repo="desktop", file="todesktop.json", label="Desktop packaged resource topology", required_patterns=( '"appFiles"', '"assets/ComfyUI/**"', '"extraResources"', '"filesForDistribution"', '"updateUrlBase"', ), source_revision=DESKTOP_REVISION, applicable_lanes=("desktop-0.9.4",), ), SurfaceCheck( repo="desktop", file="src/config/comfyServerConfig.ts", label="Desktop bundled extension topology", required_patterns=( "parsedConfig.comfyui_desktop.base_path = basePath", "path.join(getAppResourcesPath(), 'ComfyUI', 'custom_nodes')", "parsedConfig.desktop_extensions = { custom_nodes: customNodesPath }", ), source_revision=DESKTOP_REVISION, applicable_lanes=("desktop-0.9.4",), ), SurfaceCheck( repo="desktop", file="src/services/cmCli.ts", label="Desktop user custom-node restore topology", required_patterns=( "@trackEvent('migrate_flow:migrate_custom_nodes')", "path.join(this.virtualEnvironment.basePath, 'custom_nodes')", "path.join(this.virtualEnvironment.basePath, 'custom_nodes', 'ComfyUI-Manager')", ), source_revision=DESKTOP_REVISION, applicable_lanes=("desktop-0.9.4",), ), ) # CRITICAL: keep reference inspection text-only; reference repositories are untrusted evidence. def _read_text(path: Path) -> str | None: try: return path.read_text(encoding="utf-8", errors="replace") except FileNotFoundError: return None def run_checks(reference_root: Path, checks: Sequence[SurfaceCheck] = CHECKS) -> list[CheckResult]: results: list[CheckResult] = [] for check in checks: path = reference_root / check.repo / Path(check.file) text = _read_text(path) if text is None: results.append(CheckResult(check=check, ok=False, missing_file=True)) continue missing = tuple(pattern for pattern in check.required_patterns if pattern not in text) forbidden = tuple(pattern for pattern in check.forbidden_patterns if pattern in text) results.append( CheckResult( check=check, ok=not missing and not forbidden, missing_patterns=missing, present_forbidden_patterns=forbidden, ) ) return results def format_results(results: Iterable[CheckResult]) -> str: lines = ["Host compatibility smoke check:", "Frontend runtime matrix:"] for lane in FRONTEND_RUNTIME_LANES: telemetry = "present" if lane.setting_change_telemetry else "absent" lines.append( f"- {lane.id}: frontend {lane.version} " f"({lane.label}; setting-change telemetry {telemetry})" ) lines.append("Surface checks:") for result in results: prefix = "PASS" if result.ok else "FAIL" location = f"{result.check.repo}/{result.check.file}" lines.append(f"- {prefix}: {result.check.label} ({location})") if result.missing_file: lines.append(" Missing required reference file.") elif result.missing_patterns: missing = ", ".join(repr(pattern) for pattern in result.missing_patterns) lines.append(f" Missing required pattern(s): {missing}") if result.present_forbidden_patterns: present = ", ".join(repr(pattern) for pattern in result.present_forbidden_patterns) lines.append(f" Present forbidden pattern(s): {present}") if result.check.source_revision: lines.append(f" Source revision: {result.check.source_revision}") if result.check.applicable_lanes: lines.append(f" Applies to: {', '.join(result.check.applicable_lanes)}") if result.check.note: lines.append(f" Note: {result.check.note}") return "\n".join(lines) def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Validate local host reference compatibility surfaces.") parser.add_argument( "--reference-root", default="reference", type=Path, help="Path to the directory containing ComfyUI, ComfyUI_frontend, and desktop reference checkouts.", ) return parser def main(argv: Sequence[str] | None = None) -> int: args = build_parser().parse_args(argv) reference_root = args.reference_root.resolve() results = run_checks(reference_root) print(format_results(results)) return 0 if all(result.ok for result in results) else 1 if __name__ == "__main__": raise SystemExit(main())