import { readFileSync } from 'node:fs' import { join } from 'node:path' import { pathToFileURL } from 'node:url' import { isLayoutOracleSource } from './pr-layout-oracle-route.mjs' import { isCloudTestRunnerInput, isWindowsProcessProbePath, LINUX_PACKAGE_TESTS, WINDOWS_PACKAGE_TESTS } from './pr-package-test-paths.mjs' const DOCS_ONLY_FILES = new Set([ 'README.md', 'LICENSE', 'AGENTS.md', 'CLAUDE.md', 'Agents.md', 'Claude.md', '.github/CONTRIBUTING.md', '.github/pull_request_template.md', '.github/CODEOWNERS' ]) const DOCS_ONLY_PREFIXES = ['docs/', '.github/ISSUE_TEMPLATE/'] export const PR_CHECK_JOBS = [ 'static_analysis', 'typecheck', 'git_compatibility', 'codex_index_heal_contract', 'xterm_patch_sync', 'shell_contracts', 'test', 'orcad_browser', 'mobile_web_app', 'cross-version-wire', 'managed_hook_node18', 'package', 'package_windows', 'layout_oracle', 'dependency_isolation' ] const ALWAYS_ON_CODE_JOBS = new Set(['static_analysis', 'typecheck', 'test']) const MOBILE_CI_TOOL_FILES = new Set([ '.github/workflows/mobile.yml', 'config/mobile-ci.vitest.config.mjs', 'config/scripts/run-mobile-ci-tests.mjs', 'config/scripts/mobile-release-check-scope.test.mjs', 'config/scripts/mobile-test-change-scope.test.mjs' ]) const GLOBAL_FORCE_PREFIXES = [ '.github/workflows/pr.yml', '.github/actions/install-node-dependencies/', '.github/actions/setup-pnpm/', '.github/actions/restore-pnpm-verification/', '.github/actions/prepare-native-runtime/', 'config/scripts/pr-code-change-scope', 'config/scripts/pr-package-test-paths' ] const GLOBAL_FORCE_FILES = new Set(['package.json', 'pnpm-lock.yaml']) const GIT_COMPAT_PREFIXES = [ '.github/actions/prepare-git-compatibility/', 'src/shared/git-', 'src/shared/review-head-tracking-ref', 'src/shared/worktree/local-base-branch-fast-forward', 'src/main/git/', 'config/scripts/git-binary-compatibility' ] // Scope real Codex contract to read-repair/transport plus wrapper --no-daemon, project trust and hook approvals in managed/user homes. const CODEX_INDEX_HEAL_CONTRACT_PREFIXES = [ 'src/main/codex/codex-hook-file-entry-binary-contract', 'src/main/codex/codex-hook-trust-', 'src/main/codex/codex-hook-approval-first-write', 'src/main/codex/codex-hook-hash-lookup', 'src/main/codex/codex-hook-orca-approvals', 'src/main/codex/codex-hook-reconcile', 'src/main/codex/codex-hook-local-install', 'src/main/codex/codex-hook-user-mirroring', 'src/main/codex/codex-real-home-hook-', 'src/main/codex/codex-real-home-hooks-json', 'src/main/codex/codex-user-hook-trust-moves', 'src/main/codex/codex-hook-definition', 'src/main/codex/codex-hook-command-form', 'src/main/codex/codex-hook-identity', 'src/main/codex/codex-app-server-client', 'src/main/codex/codex-trust-identity', 'src/main/codex/config-toml-hook-trust-', 'src/main/codex/config-toml-key-path', 'src/main/agent-hooks/posix-hook-command', 'src/main/agent-hooks/hook-post-command', 'src/main/codex-cli/codex-read-only-app-server-args', 'src/main/agent-trust-presets', 'src/main/codex/config-toml-trust', 'src/main/pty/codex-no-daemon-binary-contract', 'src/main/pty/codex-shell-launch-preflight', 'src/shared/codex-shell-function', 'src/main/codex/codex-index-heal-binary-contract', 'src/main/codex/codex-session-index-heal', 'src/main/codex/codex-app-server-session', 'src/main/codex/codex-state-db', 'src/main/sqlite/sync-database', 'src/main/codex/app-server/codex-app-server-capability-signal', 'src/main/provider-process/provider-process-exit-deadline', 'src/main/provider-process/provider-process-launch', 'src/main/provider-process/provider-record-reader', 'src/main/codex/codex-session-backfill', 'src/main/codex/codex-session-index-heal-state', 'src/main/codex-cli/command', 'src/main/win32-utils', 'src/shared/node-cli-command-resolution', 'src/shared/windows-batch-spawn' ] const XTERM_PREFIXES = [ 'config/patches/xterm-upstream.json', 'config/patches/@xterm', 'config/patches/xterm-src/', 'config/scripts/regenerate-xterm-patches' ] const SHELL_PREFIXES = [ 'src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec', 'src/main/daemon/shell-ready', 'src/main/daemon/daemon-bash-shell-ready', 'src/main/daemon/daemon-shell-ready-wrapper', 'src/main/daemon/node-pty-fd-leak', 'src/main/providers/local-pty-shell-ready', 'src/main/providers/__tests__/shell-ready-framework-example', 'src/main/pty/', 'src/main/shell-templates', 'src/main/shell-startup-', 'src/main/shell-wrapper-', 'src/main/terminal-history-fish', 'src/main/zsh-', 'src/main/runtime/structured-session-cli-login-shell', 'src/main/runtime/structured-session-login-shell-test-harness', 'src/main/runtime/structured-session-child-env', 'src/renderer/src/components/terminal-pane/fish-color-scheme', 'src/shared/fish-', 'src/shared/pty-reply-echo-shapes', 'src/shared/startup-shell-portability', 'src/shared/posix-command-path-lookup', 'config/patches/node-pty@', 'config/scripts/ensure-native-runtime', 'config/scripts/node-pty-job-ownership' ] const ORCAD_BROWSER_PREFIXES = [ 'src/main/orcad/external-chromium-', 'src/main/orcad/orcad-browser-provider', 'src/main/orcad/orcad-agent-browser-binary', 'src/main/orcad/electron-serve-browser-process' ] // Include page build/verification/manifest/packaging, entry/routes/imported mobile sources and render-check shell policy. // Dependency visibility also matters; package.json/lockfile already force all jobs. const DEPENDENCY_ISOLATION_PREFIXES = [ 'pnpm-workspace.yaml', 'config/pnpmfile.cjs', 'config/scripts/undeclared-dependency-resolution', 'config/build-plugins/markdown-parser-exports', 'config/tsconfig', 'src/packages/', 'native/windows-registry/package.json', 'native/windows-security/package.json' ] const MOBILE_WEB_APP_PREFIXES = [ '.github/scripts/prepare-ubuntu-apt.sh', 'config/scripts/build-mobile-web-app', 'config/scripts/run-mobile-web-app-checks', 'src/packages/process-host/', 'config/scripts/verify-mobile-web-app-bundle', 'config/scripts/mobile-web-app-', 'config/scripts/mobile-web-app/', 'config/scripts/mobile-web-bundle-', 'config/scripts/verify-packaged-mobile-web-bundle', 'config/scripts/mobile-web-source-line-endings', 'config/scripts/script-entry-detection', 'mobile/web-entry/', 'mobile/app/', 'mobile/src/', 'mobile/packages/', 'mobile/package.json', 'mobile/pnpm-lock.yaml', 'mobile/modules/orca-mobile-web-shell/', // Chat visuals on both mobile surfaces are built from it; their in-page links check runs here. 'src/shared/native-chat-visual-shell' ] function changesMobileWebApp(changedFiles) { return changedFiles.some((file) => matchesPrefix(file, MOBILE_WEB_APP_PREFIXES)) } const CROSS_VERSION_WIRE_PREFIXES = [ 'tests/e2e/cross-version-wire/', // COMPAT(legacy-relay-bridge): the bridge is proven against a real 1.4.224 relay here. 'src/main/ssh/legacy-relay/', 'config/scripts/stable-release-tags', // The R1 daemon protocol crossing gate runs in this job. 'config/scripts/daemon-protocol-facts', 'config/scripts/check-daemon-protocol-crossing', // R3 runtime launcher protocol ratchet; a bump always routes here via the protocol file. 'config/scripts/check-runtime-launcher-protocol-ratchet', 'src/main/daemon/daemon-protocol-version.ts', 'src/shared/protocol-version', 'src/shared/terminal-stream-protocol', 'src/shared/browser-client-host-protocol', 'src/shared/browser-network-tunnel-protocol', // Both released/current WebSocket transports must accept web upgrade marker (cross-version-web-upgrade-marker). 'src/main/runtime/rpc/ws-transport.ts', 'src/shared/web-origin-security.ts', 'src/shared/browser-client-host-placement', 'src/shared/agent-launch-intent', 'src/shared/rpc-contract/agent-launch-params', 'src/shared/agent-session-wire', 'src/shared/agent-session-mutation-envelope', // Cover client sends to release hosts and fingerprints re-derived by host ledger/journal. 'src/shared/structured-agent-session-mutation.ts', 'src/shared/structured-agent-session-send-mutation.ts', 'src/shared/agent-session-record', 'src/shared/agent-session-provider-handle', 'src/shared/agent-session-journal-', 'src/main/ai-vault/structured-session-ownership.ts', 'src/main/native-chat/agent-session-journal/', 'src/main/native-chat/agent-session-wire/', // The released phone contract runs the current discovery owner and Claude producer. 'src/main/native-chat/agent-model-catalog/agent-model-catalog-entry.ts', 'src/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts', 'src/main/native-chat/agent-model-catalog/agent-model-catalog-service.ts', 'src/main/native-chat/agent-model-catalog/agent-model-catalog-lister.ts', 'src/main/native-chat/agent-model-catalog/agent-model-catalog-listing-waiters.ts', 'src/main/native-chat/agent-model-catalog/agent-model-catalog-probe-runner.ts', 'src/main/claude/claude-model-catalog-probe.ts', 'src/main/runtime/agent-session-record-store', 'src/main/runtime/agent-session-recovery-capsule', 'src/shared/agent-session-resume-marker', 'src/main/runtime/rpc/dispatcher', // Run on every request the suites dispatch, whatever its method. 'src/main/runtime/rpc/core.ts', 'src/main/runtime/rpc/errors.ts', 'src/main/runtime/rpc/orchestration-contract-fence.ts', 'src/main/runtime/rpc/orchestration-session-caller.ts', 'src/main/runtime/rpc/orchestration-legacy-compatibility.ts', 'src/main/runtime/rpc/orchestration-mutation-executor.ts', 'src/shared/orchestration-rpc-contract.ts', 'src/main/runtime/rpc/methods/agent-launch', 'src/main/runtime/rpc/methods/ai-vault.ts', 'src/shared/rpc-contract/browser-tab-create-params', 'src/main/runtime/rpc/methods/session-tabs.ts', 'src/main/runtime/rpc/methods/structured-agent-session', 'src/main/runtime/rpc/methods/terminal', 'src/main/runtime/runtime-worktree-agent-', 'src/main/runtime/runtime-worktree-pty-agent-sources', 'src/shared/runtime-worktree-contracts', 'src/renderer/src/runtime/remote-runtime-terminal-multiplexer', // Turn-end status a newer host publishes and an older desktop reads (cross-version-host-observed-turn-end). 'src/shared/agent-turn-outcome', 'src/shared/agent-status-types', 'src/shared/agent-lead-status-fold', 'src/shared/agent-session-turn-record', 'src/shared/structured-agent-session-agent-status', 'src/shared/structured-agent-session-projection', 'src/shared/workspace-session-sleeping-agents', // A current desktop's launch route against a released server's capabilities (cross-version-paired-structured-launch). 'src/shared/structured-native-chat-launch-route.ts', 'src/renderer/src/lib/agent-launch-routing.ts', 'src/renderer/src/runtime/paired-host-client-capabilities.ts', 'src/shared/electron-remote-runtime-client-capabilities.ts', 'src/shared/remote-runtime-client-capabilities.ts', // An older app opening a newer orchestration database (orchestration-delivery-downgrade). 'src/main/runtime/orchestration/db.ts', 'src/main/runtime/orchestration/db/', 'src/main/runtime/orchestration/orchestration-schema-version-skew' ] const MANAGED_HOOK_PREFIXES = [ 'config/scripts/smoke-process-host-node18', 'config/scripts/packaged-process-host-fixture', 'src/packages/process-host/', 'config/scripts/build-wsl-guest', 'src/wsl-guest/', 'src/shared/agent-hook', 'src/main/agent-hooks/' ] const NATIVE_RUNTIME_PREFIXES = [ 'config/scripts/ensure-native-runtime', 'config/scripts/initialize-windows-native-toolchain', 'config/scripts/capture-windows-native-environment', 'config/scripts/rebuild-native-deps', 'config/scripts/node-pty-job-ownership', 'config/scripts/windows-pe-machine', 'config/scripts/windows-pe-image-fixture', 'config/scripts/script-module-dependencies', 'config/scripts/windows-process-tree-creation-time', 'config/scripts/windows-process-tree-gyp-rebuild', 'config/scripts/electron-builder-native-rebuild', 'config/scripts/native-build-entry', 'config/scripts/shared-native-build', 'config/patches/node-pty@', 'config/patches/@vscode__windows-process-tree' ] const NATIVE_CACHE_FILES = new Set([ 'package.json', 'pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', '.github/actions/prepare-native-runtime/action.yml', 'pnpm-workspace.yaml', '.npmrc', 'config/pnpmfile.cjs', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/initialize-windows-native-toolchain.mjs', 'config/scripts/capture-windows-native-environment.cmd', 'config/scripts/rebuild-native-deps.mjs', 'config/scripts/node-pty-job-ownership.cjs', 'config/scripts/windows-pe-machine.cjs', 'config/scripts/windows-process-tree-gyp-rebuild.mjs', 'config/scripts/windows-process-tree-creation-time.cjs', 'config/scripts/install-electron-package-binary.mjs', 'config/scripts/electron-platform-path.mjs', 'config/scripts/zip-extractor-command.mjs', 'src/shared/zip-extractor-command.ts', 'config/scripts/shared-electron-dist-cache.mjs', 'config/scripts/shared-tree-publish.mjs', 'config/scripts/space-sharing-copy.mjs', 'config/scripts/native-build-entry.cjs', 'config/scripts/native-build-entry-probe.cjs', 'config/scripts/shared-native-build.mjs' ]) const NATIVE_CACHE_PREFIXES = [ 'config/patches/node-pty@', 'config/patches/@vscode__windows-process-tree', // The vendored addons' sources: build/ and bin/ beside them are ignored, so nothing else lands here. 'native/windows-registry/', 'native/windows-security/' ] const SHARED_PACKAGE_PREFIXES = [ 'electron.vite.config.ts', 'config/electron-builder', 'config/packaged-runtime', 'config/packaged-native-build-entries', 'config/build-plugins/', 'config/scripts/build-', 'config/scripts/smoke-packaged', 'config/scripts/install-electron-package-binary', 'config/scripts/verify-packaged', 'config/scripts/verify-skills-cli-runtime', 'config/scripts/verify-linux-glibc', 'config/scripts/run-electron-vite', 'skills/', 'skill-guides/', 'resources/build/', 'resources/onboarding/', 'resources/plugins/', 'resources/skills/', ...NATIVE_RUNTIME_PREFIXES ] const LINUX_PACKAGE_PREFIXES = [ ...SHARED_PACKAGE_PREFIXES, 'config/scripts/package-linux-formats', 'config/scripts/process-failure-message.mjs', 'config/scripts/space-sharing-copy.mjs', '.github/actions/prepare-linux-package-fixture/', '.github/actions/prepare-wayland-clipboard/', 'config/docker/cli-launch-contract/', 'config/docker/headless-pairing/', 'config/docker/headless-serve-shutdown/', 'config/docker/daemon-shutdown-descendants/', 'config/scripts/run-linux-cli-launch-contract', 'config/scripts/run-headless-linux-pairing-docker', 'config/scripts/run-daemon-shutdown-descendants-docker', 'config/scripts/static-appimage-package-contract', 'native/computer-use-linux/', 'native/wayland-clipboard/', 'resources/linux/', 'config/scripts/run-headless-serve' ] const WINDOWS_PACKAGE_PREFIXES = [ ...SHARED_PACKAGE_PREFIXES, 'native/windows-cli-launcher/', 'native/windows-agent-hook/', 'native/computer-use-windows/', 'resources/win32/', 'config/scripts/build-windows-cli-launcher', 'config/scripts/build-windows-agent-hook', 'config/scripts/windows-rust-executable-build', 'config/scripts/verify-packaged-windows-agent-hook', 'config/scripts/windows-pty-native-capability', 'tests/tools/windows-pty-native-capability-smoke/' ] const DESKTOP_IRRELEVANT_PREFIXES = [ 'mobile/', 'cloud/', '.github/workflows/cloud-', '.github/workflows/mobile.yml', '.github/workflows/mobile-ios-release.yml', '.github/workflows/mobile-android-release.yml' ] const STATIC_ANALYSIS_AUDIT_SCRIPTS = [ 'lint:js-plugins', 'audit:code-quality:native', 'audit:code-quality:type-aware', 'audit:anti-slop' ] // oxlint positional args name lint trees; --config consumes one value, other listed flags none. function oxlintScanRoots(command) { const roots = [] for (const segment of command.split('&&')) { const tokens = segment.trim().split(/\s+/).filter(Boolean) if (tokens[0] !== 'oxlint') { continue } for (let index = 1; index < tokens.length; index += 1) { if (tokens[index] === '--config') { index += 1 } else if (!tokens[index].startsWith('-')) { roots.push(tokens[index]) } } } return roots } // Derive lint trees from command argv: mobile is otherwise desktop-irrelevant, and duplicate scope lists let #20702 bypass audits. function readStaticAnalysisScanRoots() { const manifest = join(import.meta.dirname, '../../package.json') const { scripts = {} } = JSON.parse(readFileSync(manifest, 'utf8')) return [ ...new Set( STATIC_ANALYSIS_AUDIT_SCRIPTS.flatMap((name) => oxlintScanRoots(scripts[name] ?? '')) ) ] } export const STATIC_ANALYSIS_SCAN_ROOTS = readStaticAnalysisScanRoots() const STATIC_ANALYSIS_SCAN_PREFIXES = STATIC_ANALYSIS_SCAN_ROOTS.map((root) => `${root}/`) // Over-inclusive on purpose: a type oxfmt skips only costs this PR a static run. const FORMAT_CHECKED_EXTENSION = /\.(?:[cm]?[jt]sx?|json[c5]?|css|less|scss|md|mdx|ya?ml|vue|html|graphql|gql|toml)$/i function readFormatIgnoredPrefixes() { let config try { config = JSON.parse(readFileSync(join(import.meta.dirname, '../../.oxfmtrc.json'), 'utf8')) } catch { // Fail closed: with no ignores known, every formatted type keeps the gate. return [] } return (config.ignorePatterns ?? []) .filter((pattern) => pattern.endsWith('/**')) .map((pattern) => pattern.slice(0, -2)) } const FORMAT_IGNORED_PREFIXES = readFormatIgnoredPrefixes() export function isDocsOnlyPath(file) { return ( DOCS_ONLY_FILES.has(file) || DOCS_ONLY_PREFIXES.some((prefix) => file.startsWith(prefix)) || /^README\.[^/]+\.md$/.test(file) ) } export function shouldRunPrChecks(changedFiles) { // Empty diff likely signals detector failure; fail closed and retain expensive jobs. if (changedFiles.length === 0) { return true } return changedFiles.some((file) => !isDocsOnlyPath(file) && !isDesktopIrrelevantPath(file)) } export function needsMobileDependencies(changedFiles) { // Changed-mobile type-aware lint needs mobile/node_modules: root-only install resolves mobile types as error and produces phantom findings. return changedFiles.length === 0 || changedFiles.some((file) => file.startsWith('mobile/')) } export const DAEMON_INIT_TEST_SUPPORT = 'src/main/daemon/daemon-init-dependency-mocks.ts' const DAEMON_INIT_TEST_SCOPE = new Set([ DAEMON_INIT_TEST_SUPPORT, 'src/main/native-chat/agent-session-wire/structured-agent-session-claude-queued-stop.test.ts', 'config/scripts/process-host-build-dist.test.mjs' ]) export function isDaemonInitTestSupportChange(files, modifiedOnly = false) { return ( modifiedOnly && files.includes(DAEMON_INIT_TEST_SUPPORT) && new Set(files).size === files.length && files.every((file) => DAEMON_INIT_TEST_SCOPE.has(file)) ) } export function classifyPrJobs( changedFiles, { allowMobileCiTools = false, allowDaemonInitTestSupport = false } = {} ) { const emptyDiff = changedFiles.length === 0 const shouldRun = shouldRunPrChecks(changedFiles) const forceAll = emptyDiff || changedFiles.some(isGlobalForcePath) const bundleFiles = isDaemonInitTestSupportChange(changedFiles, allowDaemonInitTestSupport) ? changedFiles.filter((file) => file !== DAEMON_INIT_TEST_SUPPORT) : changedFiles const jobs = Object.fromEntries( PR_CHECK_JOBS.map((job) => [ job, shouldRun && (forceAll || ALWAYS_ON_CODE_JOBS.has(job) || jobDetector(job)( ['package', 'package_windows'].includes(job) ? bundleFiles : changedFiles )) ]) ) // Keep repo-wide audits outside desktop should_run: mobile-only diffs still need lint; formatted/docs paths also require whole-tree formatting. jobs.static_analysis = jobs.static_analysis || changedFiles.some((file) => isStaticAnalysisScannedPath(file) || isFormatCheckedPath(file)) // Page-bundle gate must bypass desktop should_run or mobile-only diffs skip the very build they change. jobs.mobile_web_app = jobs.mobile_web_app || changesMobileWebApp(changedFiles) const mobileCiTools = allowMobileCiTools && !emptyDiff && jobs.test && (jobs.static_analysis || jobs.typecheck) && changedFiles.every((file) => MOBILE_CI_TOOL_FILES.has(file)) return { should_run: shouldRun, mobile_ci_tools: mobileCiTools, windows_process_probe: shouldRun && (forceAll || changedFiles.some(isWindowsProcessProbePath)), // Relay contracts exercise host/cloud code; any input outside the renderer keeps them. relay_integration: shouldRun && (forceAll || changedFiles.some((file) => !file.startsWith('src/renderer/') && !isDocsOnlyPath(file))), native_cache_changed: shouldRun && (emptyDiff || changedFiles.some(isNativeCacheInputPath)), mobile_dependencies: (shouldRun || jobs.static_analysis) && needsMobileDependencies(changedFiles), ...jobs } } function jobDetector(job) { switch (job) { case 'git_compatibility': return (files) => files.some((file) => matchesPrefix(file, GIT_COMPAT_PREFIXES)) case 'codex_index_heal_contract': return (files) => files.some( (file) => !/^src\/main\/codex\/codex-app-server-(client|session)\.test\.ts(?!.)/s.test(file) && matchesPrefix(file, CODEX_INDEX_HEAL_CONTRACT_PREFIXES) ) case 'xterm_patch_sync': return (files) => files.some((file) => matchesPrefix(file, XTERM_PREFIXES)) case 'shell_contracts': return (files) => files.some((file) => matchesPrefix(file, SHELL_PREFIXES)) case 'orcad_browser': return (files) => files.some((file) => matchesPrefix(file, ORCAD_BROWSER_PREFIXES)) // Handle inside map too: default detector otherwise runs this job on every desktop-relevant PR. case 'mobile_web_app': return changesMobileWebApp case 'cross-version-wire': return (files) => files.some((file) => matchesPrefix(file, CROSS_VERSION_WIRE_PREFIXES)) case 'managed_hook_node18': return (files) => files.some((file) => matchesPrefix(file, MANAGED_HOOK_PREFIXES)) case 'package': return (files) => files.some(isLinuxPackagePath) case 'package_windows': return (files) => files.some(isWindowsPackagePath) case 'layout_oracle': return (files) => files.some(isLayoutOracleSource) case 'dependency_isolation': return (files) => files.some((file) => matchesPrefix(file, DEPENDENCY_ISOLATION_PREFIXES)) default: return () => true } } function isLinuxPackagePath(file) { return LINUX_PACKAGE_TESTS.includes(file) || isProductBundlePath(file, LINUX_PACKAGE_PREFIXES) } function isWindowsPackagePath(file) { return ( WINDOWS_PACKAGE_TESTS.includes(file) || isWindowsProcessProbePath(file) || isProductBundlePath(file, WINDOWS_PACKAGE_PREFIXES) ) } function isProductBundlePath(file, extraPrefixes) { return !isTestFile(file) && (file.startsWith('src/') || matchesPrefix(file, extraPrefixes)) } // These shared fixtures are consumed only by unit tests. export function isUnitTestSupportSource(file) { return ( file === 'src/renderer/src/store/slices/editor-slice-test-harness.ts' || file === 'src/renderer/src/store/slices/browser-slice-test-harness.ts' || file === 'src/renderer/src/runtime/web-runtime-session-test-harness.ts' || file === 'src/renderer/src/runtime/sync-runtime-graph-test-harness.ts' || file === 'src/renderer/src/runtime/web-session-tabs-sync-test-harness.ts' || file === 'src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx' ) } function isTestFile(file) { return ( isUnitTestSupportSource(file) || /\.(?:test|spec)\.(?:js|cjs|mjs|ts|tsx)$/.test(file) || file.includes('/__tests__/') ) } function isDesktopIrrelevantPath(file) { return matchesPrefix(file, DESKTOP_IRRELEVANT_PREFIXES) } function isStaticAnalysisScannedPath(file) { // Fail closed: roots we failed to parse must keep the gate, not silently drop it. return ( STATIC_ANALYSIS_SCAN_PREFIXES.length === 0 || matchesPrefix(file, STATIC_ANALYSIS_SCAN_PREFIXES) || isCloudTestRunnerInput(file) ) } function isFormatCheckedPath(file) { return FORMAT_CHECKED_EXTENSION.test(file) && !matchesPrefix(file, FORMAT_IGNORED_PREFIXES) } function isNativeCacheInputPath(file) { return NATIVE_CACHE_FILES.has(file) || matchesPrefix(file, NATIVE_CACHE_PREFIXES) } function isGlobalForcePath(file) { return ( GLOBAL_FORCE_FILES.has(file) || (matchesPrefix(file, GLOBAL_FORCE_PREFIXES) && !(file.startsWith('config/scripts/') && isTestFile(file))) ) } function matchesPrefix(file, prefixes) { return prefixes.some((prefix) => file === prefix || file.startsWith(prefix)) } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { // Stream stdin: readFileSync(0) can throw EAGAIN beyond the 64KB pipe buffer on stale-base diffs. let input = '' process.stdin.setEncoding('utf8') for await (const chunk of process.stdin) { input += chunk } const classification = classifyPrJobs(input.split(/\r?\n/).filter(Boolean), { allowMobileCiTools: process.argv.includes('--mobile-ci-tools'), allowDaemonInitTestSupport: process.argv.includes('--daemon-init-test-support') }) for (const [name, value] of Object.entries(classification)) { process.stdout.write(`${name}=${value ? 'true' : 'false'}\n`) } }