import { execFileSync, spawnSync } from 'node:child_process'import { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'import { tmpdir } from 'node:os'import { join, relative, resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { classifyChanges, parseNameStatus, platformExecutionPlan, toGitHubOutputPlan} from './classify-pr-changes.mjs'import { createAffectedTestPlan } from './module-test-impact.mjs' const readManifest = (): ReturnType<JSON['parse']> => JSON.parse(readFileSync(resolve('scripts/ci/change-impact.json'), 'utf8')) const listSourceFiles = (directory: string): string[] => readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const path = join(directory, entry.name) return entry.isDirectory() ? listSourceFiles(path) : [path] }) const windowsRuntimeSignal = /['"]win32['"]|powershell|taskkill(?:\.exe)?|windowsHide|SystemRoot|WINDIR|USERPROFILE|ProgramFiles|LOCALAPPDATA|APPDATA/i describe('pull request change classification', () => { it('publishes a Git revision plan for GitHub Actions callers', () => { const root = mkdtempSync(join(tmpdir(), 'pr-change-classifier-')) const output = join(root, 'github-output') const summary = join(root, 'github-summary') try { execFileSync('git', ['init', '--quiet'], { cwd: root }) execFileSync('git', ['config', 'user.email', 'ci@example.com'], { cwd: root }) execFileSync('git', ['config', 'user.name', 'CI Test'], { cwd: root }) writeFileSync(join(root, 'README.md'), '# fixture\n') execFileSync('git', ['add', 'README.md'], { cwd: root }) execFileSync('git', ['commit', '--quiet', '-m', 'initial'], { cwd: root }) const base = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim() mkdirSync(join(root, 'src', 'shared'), { recursive: true }) writeFileSync(join(root, 'src', 'shared', 'acp.ts'), 'export type Acp = unknown\n') execFileSync('git', ['add', 'src/shared/acp.ts'], { cwd: root }) execFileSync('git', ['commit', '--quiet', '-m', 'add shared contract'], { cwd: root }) const head = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim() const result = spawnSync( process.execPath, [resolve('scripts/ci/classify-pr-changes.mjs'), '--base', base, '--head', head], { cwd: root, encoding: 'utf8', env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: summary } } ) expect(result.status, result.stderr).toBe(0) const outputs = Object.fromEntries( readFileSync(output, 'utf8') .trim() .split('\n') .map((line) => line.split('=', 2)) ) expect(JSON.parse(outputs.lanes)).toContain('e2e_workspace_macos') expect(JSON.parse(outputs.plan)).toEqual({ schemaVersion: 1, mode: 'selective', roots: expect.any(Array), lanes: expect.any(Array), bundles: expect.arrayContaining(['policy', 'static', 'unit', 'macos_e2e']), macosGroups: ['journeys', 'presentation', 'regressions', 'delegation'] }) expect(JSON.parse(outputs.plan)).not.toHaveProperty('reasonChains') expect(readFileSync(summary, 'utf8')).toContain( 'src/shared/acp.ts -> shared_contract -> preload_adapter' ) expect(readFileSync(summary, 'utf8')).toContain( 'Execution bundles: policy, static, unit, macos_e2e' ) } finally { rmSync(root, { force: true, recursive: true }) } }) it('keeps GitHub Actions plan output bounded without per-path reason chains', () => { const linuxMaxArgStrlen = 131_072 const changes = Array.from({ length: 2_000 }, (_, index) => ({ path: `src/renderer/src/pages/workspace/generated-${index}.tsx`, status: 'modified' as const })) const plan = classifyChanges(changes) const output = toGitHubOutputPlan(plan) const outputJson = JSON.stringify(output) expect(plan.reasonChains.length).toBeGreaterThan(1_000) expect(JSON.stringify(plan).length).toBeGreaterThan(linuxMaxArgStrlen) expect(output).not.toHaveProperty('reasonChains') expect(outputJson).not.toContain('reasonChains') expect(outputJson.length).toBeLessThan(8_192) expect(output).toMatchObject({ schemaVersion: plan.schemaVersion, mode: plan.mode, roots: plan.roots, lanes: plan.lanes, bundles: plan.bundles }) }) it('preserves both paths from NUL-delimited Git rename and deletion records', () => { const changes = parseNameStatus( 'M\0src/main/index.ts\0R097\0src/shared/old.ts\0src/shared/new.ts\0D\0README.md\0' ) expect(changes).toEqual([ { path: 'src/main/index.ts', status: 'modified' }, { path: 'src/shared/new.ts', previousPath: 'src/shared/old.ts', status: 'renamed' }, { path: 'README.md', status: 'deleted' } ]) }) it('expands a Main IPC change through its Renderer consumers', () => { const plan = classifyChanges([{ path: 'src/main/settings/ipc.ts', status: 'modified' }]) expect(plan.mode).toBe('selective') expect(plan.lanes).toEqual( expect.arrayContaining([ 'typecheck_node', 'typecheck_web', 'interface_contracts', 'unit_macos', 'build', 'e2e_functional_macos', 'e2e_workspace_macos' ]) ) expect(plan.reasonChains).toContain( 'src/main/settings/ipc.ts -> settings_ipc -> preload_adapter -> renderer_settings -> e2e_workspace' ) }) it('fails closed to the full deterministic plan for an unknown path', () => { const plan = classifyChanges([{ path: 'src/new-runtime/capability.ts', status: 'added' }]) expect(plan.mode).toBe('full') expect(plan.roots).toContain('unknown') expect(plan.lanes).toEqual( expect.arrayContaining([ 'lint', 'typecheck_node', 'typecheck_web', 'unit_macos', 'windows_runtime', 'e2e_functional_macos', 'e2e_functional_windows', 'e2e_workspace_macos', 'e2e_workspace_windows', 'e2e_accessibility_macos', 'e2e_visual_macos' ]) ) expect(plan.bundles).toEqual([ 'policy', 'static', 'unit', 'linux_runtime', 'windows_core', 'macos_e2e', 'windows_e2e' ]) expect(plan.reasonChains).toContain('src/new-runtime/capability.ts -> unknown -> full') }) it('selects cross-platform build and runtime lanes for the Notebook network sandbox', () => { const path = 'packages/notebook-network-sandbox/src/config.ts' const plan = classifyChanges([{ path, status: 'modified' }]) expect(plan.mode).toBe('selective') expect(plan.roots).toContain('notebook_network_sandbox') expect(plan.lanes).toEqual( expect.arrayContaining([ 'typecheck_node', 'unit_macos', 'linux_runtime', 'windows_runtime', 'windows_path', 'build' ]) ) expect(plan.bundles).toContain('linux_runtime') expect(plan.reasonChains).toContain(`${path} -> notebook_network_sandbox`) }) it('fails closed when a selected lane has no execution bundle', () => { const manifest = readManifest() delete manifest.laneBundles.policy expect(() => classifyChanges([], manifest)).toThrow( 'Missing execution bundle for selected lane: policy' ) }) it('fails closed when a selected lane references an undeclared execution bundle', () => { const manifest = readManifest() manifest.laneBundles.policy = 'undeclared' expect(() => classifyChanges([], manifest)).toThrow( 'Unknown execution bundle for selected lane policy: undeclared' ) }) it('keeps documentation-only changes on the stable minimal gate', () => { const plan = classifyChanges([ { path: 'docs/internal/pr-gate.md', status: 'modified' }, { path: 'README.md', status: 'modified' } ]) expect(plan.mode).toBe('selective') expect(plan.lanes).toEqual(['policy', 'docs']) expect(plan.bundles).toEqual(['policy', 'static']) expect(plan.reasonChains).toEqual( expect.arrayContaining([ 'README.md -> documentation', 'docs/internal/pr-gate.md -> documentation' ]) ) }) it.each(['yml', 'yaml'])('keeps issue-template .%s changes on static checks', (extension) => { const changes = [ { path: `.github/ISSUE_TEMPLATE/reproducibility_case.${extension}`, status: 'added' }, { path: '.github/ISSUE_TEMPLATE/config.yml', status: 'modified' }, { path: 'CONTRIBUTING.md', status: 'modified' }, { path: 'docs/reproducibility-cases/README.md', status: 'added' }, { path: 'docs/reproducibility-cases/index.md', status: 'added' } ] const plan = classifyChanges(changes) expect(plan.mode).toBe('selective') expect(plan.lanes).toEqual(['policy', 'docs', 'format']) expect(plan.bundles).toEqual(['policy', 'static']) for (const event of ['pull_request', 'merge_group']) { expect(platformExecutionPlan(plan, changes, event).bundles).toEqual(['policy', 'static']) } const tests = createAffectedTestPlan(changes, { status: 'unavailable-manifest-only', testFiles: [] }) expect(tests.mode).toBe('selective') expect(tests.testFiles).toEqual([]) }) it.each([ '.github/ISSUE_TEMPLATE/helper.js', '.github/ISSUE_TEMPLATE/nested/form.yml', '.github/unknown.yml', '.github/workflows/pr-gate.yml' ])('does not exempt unknown or executable inputs: %s', (path) => { expect(classifyChanges([{ path, status: 'added' }]).mode).toBe('full') }) it('retains the full fallback when an issue form accompanies runtime changes', () => { const plan = classifyChanges([ { path: '.github/ISSUE_TEMPLATE/bug_report.yml', status: 'modified' }, { path: 'src/unknown-runtime.ts', status: 'added' } ]) expect(plan.mode).toBe('full') expect(plan.bundles).toEqual(expect.arrayContaining(['unit', 'windows_core', 'windows_e2e'])) }) it('uses one specific owner instead of a broad fallback owner', () => { const manifest = readManifest() manifest.rules.push({ id: 'notebook_runtime', role: 'owner', paths: ['src/main/notebook/runtime-service.ts'], capabilities: ['notebook_runtime'] }) manifest.capabilities.notebook_runtime = { consumers: [], lanes: ['typecheck_node'] } const plan = classifyChanges( [{ path: 'src/main/notebook/runtime-service.ts', status: 'modified' }], manifest ) expect(plan.mode).toBe('selective') expect(plan.roots).toContain('notebook_runtime') expect(plan.roots).not.toContain('main_runtime') expect(plan.lanes).toEqual([ 'policy', 'typecheck_node', 'e2e_regressions_macos', 'e2e_delegation_macos' ]) }) it('keeps risk overlays additive after a specific owner replaces a fallback', () => { const manifest = readManifest() manifest.rules.push({ id: 'notebook_windows_runtime', role: 'owner', paths: ['src/main/notebook/windows-runtime.ts'], capabilities: ['notebook_runtime'] }) manifest.capabilities.notebook_runtime = { consumers: [], lanes: ['typecheck_node'] } const path = 'src/main/notebook/windows-runtime.ts' const plan = classifyChanges([{ path, status: 'modified' }], manifest) expect(plan.mode).toBe('selective') expect(plan.roots).toEqual( expect.arrayContaining(['notebook_windows_runtime', 'windows_sensitive']) ) expect(plan.roots).not.toContain('main_runtime') expect(plan.lanes).toEqual( expect.arrayContaining(['typecheck_node', 'windows_runtime', 'windows_path']) ) }) it('fails closed when multiple specific owners match one path', () => { const manifest = readManifest() for (const id of ['notebook_runtime_a', 'notebook_runtime_b']) { manifest.rules.push({ id, role: 'owner', paths: ['src/main/notebook/runtime-service.ts'], capabilities: ['main_runtime'] }) } const path = 'src/main/notebook/runtime-service.ts' const plan = classifyChanges([{ path, status: 'modified' }], manifest) expect(plan.mode).toBe('full') expect(plan.roots).toContain('owner_ambiguity') expect(plan.reasonChains).toContain( `${path} -> owner ambiguity: notebook_runtime_a, notebook_runtime_b -> full` ) }) it('fails closed when a matched path has no owner', () => { const manifest = readManifest() manifest.rules.push({ id: 'unowned_overlay', role: 'overlay', paths: ['src/new-runtime/overlay.ts'], capabilities: ['ci_integrity_surface'] }) const path = 'src/new-runtime/overlay.ts' const plan = classifyChanges([{ path, status: 'added' }], manifest) expect(plan.mode).toBe('full') expect(plan.roots).toContain('missing_owner') expect(plan.reasonChains).toContain(`${path} -> missing owner -> full`) }) it('treats Shared changes as cross-process consumer changes', () => { const plan = classifyChanges([{ path: 'src/shared/acp.ts', status: 'modified' }]) expect(plan.mode).toBe('selective') expect(plan.lanes).toEqual( expect.arrayContaining([ 'format', 'lint', 'typecheck_node', 'typecheck_web', 'interface_contracts', 'unit_macos', 'build', 'e2e_functional_macos', 'e2e_workspace_macos', 'e2e_accessibility_macos', 'e2e_visual_macos' ]) ) expect(plan.reasonChains).toContain( 'src/shared/acp.ts -> shared_contract -> preload_adapter -> renderer_settings -> e2e_workspace' ) }) it('expands Main runtime changes to desktop behavior consumers', () => { const plan = classifyChanges([ { path: 'src/main/notebook/runtime-service.ts', status: 'modified' } ]) expect(plan.mode).toBe('selective') expect(plan.lanes).toEqual( expect.arrayContaining([ 'format', 'lint', 'typecheck_node', 'typecheck_web', 'interface_contracts', 'unit_macos', 'build', 'e2e_functional_macos', 'e2e_workspace_macos' ]) ) expect(plan.lanes).not.toContain('e2e_visual_macos') expect(plan.reasonChains).toContain( 'src/main/notebook/runtime-service.ts -> main_runtime -> renderer_runtime -> e2e_workspace' ) }) it.each([ ['Windows runtime', 'src/main/windows.ts'], ['Shell search scope', 'src/main/notebook/shell-search-scope.ts'], ['PowerShell search parser', 'src/main/notebook/powershell-search-parser.ts'], ['PowerShell', 'src/main/notebook/micromamba-cache-powershell.test.ts'], ['path handling', 'src/main/acp/workspace-path.ts'], ['ACL behavior', 'src/main/notebook/micromamba-cache-acl.integration.test.ts'], ['Windows wheel recovery', 'src/main/notebook/pip-wheel-evidence.ts'], ['Windows wheel regression', 'src/main/notebook/pip-wheel-evidence.test.ts'], ['Windows install evidence', 'src/main/notebook/pip-install-evidence.test.ts'], ['storage', 'src/main/storage/ipc.ts'], ['session persistence', 'src/main/session-persistence/ipc.ts'], ['delegated process ownership', 'src/main/delegation/process-ownership.ts'], ['notebook shell process', 'src/main/notebook/shell-process.ts'], ['file save', 'src/main/file-save.ts'], ['specialist repository', 'src/main/specialist/repository.ts'], ['notebook runtime settings', 'src/main/settings/notebook-runtime-settings.ts'], ['preferences', 'src/main/settings/preferences.ts'], ['restricted runtime profile', 'src/main/acp/restricted-runtime-profile.ts'], ['CodeBuddy framework', 'src/main/agent-framework/codebuddy.ts'], ['CodeBuddy detect', 'src/main/settings/codebuddy-detect.ts'], ['managed CodeBuddy', 'src/main/settings/managed-codebuddy.ts'], ['immutable notebook inputs', 'src/main/immutable-input-authority.ts'], ['notebook package process sandbox', 'src/main/notebook/package-process-sandbox.ts'], ['WSL setup ownership', 'src/main/wsl/wsl-setup-owner.ts'], ['window shortcuts', 'src/main/window-shortcuts.ts'] ])('adds native Windows lanes for %s changes', (_category, path) => { const plan = classifyChanges([{ path, status: 'modified' }]) expect(plan.roots).toContain('windows_sensitive') expect(plan.lanes).toEqual(expect.arrayContaining(['windows_runtime', 'windows_path'])) expect(plan.reasonChains).toContain(`${path} -> windows_sensitive`) }) it('requires a live CDN bundle when the immutable runtime version changes', () => { const plan = classifyChanges([ { path: 'src/main/notebook/runtime-paths.ts', status: 'modified' } ]) expect(plan.roots).toEqual(expect.arrayContaining(['main_runtime', 'runtime_bundle'])) expect(plan.lanes).toContain('runtime_bundle') expect(plan.reasonChains).toContain('src/main/notebook/runtime-paths.ts -> runtime_bundle') }) it('does not add focused Windows lanes for platform-neutral Main changes', () => { const plan = classifyChanges([ { path: 'src/main/notebook/runtime-service.ts', status: 'modified' } ]) expect(plan.roots).not.toContain('windows_sensitive') expect(plan.lanes).not.toContain('windows_runtime') expect(plan.lanes).not.toContain('windows_path') }) it.each([ ['renderer view', 'src/renderer/src/components/Button.tsx'], ['renderer locale catalog', 'src/shared/i18n/locales/ja.json'], ['Korean locale catalog', 'src/shared/i18n/locales/ko.json'], ['French locale catalog', 'src/shared/i18n/locales/fr.json'], ['Spanish locale catalog', 'src/shared/i18n/locales/es.json'], ['German locale catalog', 'src/shared/i18n/locales/de.json'], ['shared contract', 'src/shared/acp.ts'], ['main runtime', 'src/main/notebook/runtime-service.ts'] ])('selects the i18n catalog lane for a scanned %s change', (_label, path) => { const plan = classifyChanges([{ path, status: 'modified' }]) expect(plan.lanes).toContain('i18n') expect(plan.bundles).toContain('static') }) it.each( readdirSync(resolve('src/shared/i18n/locales')) .filter((name) => name.endsWith('.json')) .map((name) => name.replace(/\.json$/u, ''))Lines 1–500 of 742. Load more
import { execFileSync, spawnSync } from 'node:child_process' import { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join, relative, resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { classifyChanges, parseNameStatus, platformExecutionPlan, toGitHubOutputPlan } from './classify-pr-changes.mjs' import { createAffectedTestPlan } from './module-test-impact.mjs' const readManifest = (): ReturnType<JSON['parse']> => JSON.parse(readFileSync(resolve('scripts/ci/change-impact.json'), 'utf8')) const listSourceFiles = (directory: string): string[] => readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const path = join(directory, entry.name) return entry.isDirectory() ? listSourceFiles(path) : [path] }) const windowsRuntimeSignal = /['"]win32['"]|powershell|taskkill(?:\.exe)?|windowsHide|SystemRoot|WINDIR|USERPROFILE|ProgramFiles|LOCALAPPDATA|APPDATA/i describe('pull request change classification', () => { it('publishes a Git revision plan for GitHub Actions callers', () => { const root = mkdtempSync(join(tmpdir(), 'pr-change-classifier-')) const output = join(root, 'github-output') const summary = join(root, 'github-summary') try { execFileSync('git', ['init', '--quiet'], { cwd: root }) execFileSync('git', ['config', 'user.email', 'ci@example.com'], { cwd: root }) execFileSync('git', ['config', 'user.name', 'CI Test'], { cwd: root }) writeFileSync(join(root, 'README.md'), '# fixture\n') execFileSync('git', ['add', 'README.md'], { cwd: root }) execFileSync('git', ['commit', '--quiet', '-m', 'initial'], { cwd: root }) const base = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim() mkdirSync(join(root, 'src', 'shared'), { recursive: true }) writeFileSync(join(root, 'src', 'shared', 'acp.ts'), 'export type Acp = unknown\n') execFileSync('git', ['add', 'src/shared/acp.ts'], { cwd: root }) execFileSync('git', ['commit', '--quiet', '-m', 'add shared contract'], { cwd: root }) const head = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim() const result = spawnSync( process.execPath, [resolve('scripts/ci/classify-pr-changes.mjs'), '--base', base, '--head', head], { cwd: root, encoding: 'utf8', env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: summary } } ) expect(result.status, result.stderr).toBe(0) const outputs = Object.fromEntries( readFileSync(output, 'utf8') .trim() .split('\n') .map((line) => line.split('=', 2)) ) expect(JSON.parse(outputs.lanes)).toContain('e2e_workspace_macos') expect(JSON.parse(outputs.plan)).toEqual({ schemaVersion: 1, mode: 'selective', roots: expect.any(Array), lanes: expect.any(Array), bundles: expect.arrayContaining(['policy', 'static', 'unit', 'macos_e2e']), macosGroups: ['journeys', 'presentation', 'regressions', 'delegation'] }) expect(JSON.parse(outputs.plan)).not.toHaveProperty('reasonChains') expect(readFileSync(summary, 'utf8')).toContain( 'src/shared/acp.ts -> shared_contract -> preload_adapter' ) expect(readFileSync(summary, 'utf8')).toContain( 'Execution bundles: policy, static, unit, macos_e2e' ) } finally { rmSync(root, { force: true, recursive: true }) } }) it('keeps GitHub Actions plan output bounded without per-path reason chains', () => { const linuxMaxArgStrlen = 131_072 const changes = Array.from({ length: 2_000 }, (_, index) => ({ path: `src/renderer/src/pages/workspace/generated-${index}.tsx`, status: 'modified' as const })) const plan = classifyChanges(changes) const output = toGitHubOutputPlan(plan) const outputJson = JSON.stringify(output) expect(plan.reasonChains.length).toBeGreaterThan(1_000) expect(JSON.stringify(plan).length).toBeGreaterThan(linuxMaxArgStrlen) expect(output).not.toHaveProperty('reasonChains') expect(outputJson).not.toContain('reasonChains') expect(outputJson.length).toBeLessThan(8_192) expect(output).toMatchObject({ schemaVersion: plan.schemaVersion, mode: plan.mode, roots: plan.roots, lanes: plan.lanes, bundles: plan.bundles }) }) it('preserves both paths from NUL-delimited Git rename and deletion records', () => { const changes = parseNameStatus( 'M\0src/main/index.ts\0R097\0src/shared/old.ts\0src/shared/new.ts\0D\0README.md\0' ) expect(changes).toEqual([ { path: 'src/main/index.ts', status: 'modified' }, { path: 'src/shared/new.ts', previousPath: 'src/shared/old.ts', status: 'renamed' }, { path: 'README.md', status: 'deleted' } ]) }) it('expands a Main IPC change through its Renderer consumers', () => { const plan = classifyChanges([{ path: 'src/main/settings/ipc.ts', status: 'modified' }]) expect(plan.mode).toBe('selective') expect(plan.lanes).toEqual( expect.arrayContaining([ 'typecheck_node', 'typecheck_web', 'interface_contracts', 'unit_macos', 'build', 'e2e_functional_macos', 'e2e_workspace_macos' ]) ) expect(plan.reasonChains).toContain( 'src/main/settings/ipc.ts -> settings_ipc -> preload_adapter -> renderer_settings -> e2e_workspace' ) }) it('fails closed to the full deterministic plan for an unknown path', () => { const plan = classifyChanges([{ path: 'src/new-runtime/capability.ts', status: 'added' }]) expect(plan.mode).toBe('full') expect(plan.roots).toContain('unknown') expect(plan.lanes).toEqual( expect.arrayContaining([ 'lint', 'typecheck_node', 'typecheck_web', 'unit_macos', 'windows_runtime', 'e2e_functional_macos', 'e2e_functional_windows', 'e2e_workspace_macos', 'e2e_workspace_windows', 'e2e_accessibility_macos', 'e2e_visual_macos' ]) ) expect(plan.bundles).toEqual([ 'policy', 'static', 'unit', 'linux_runtime', 'windows_core', 'macos_e2e', 'windows_e2e' ]) expect(plan.reasonChains).toContain('src/new-runtime/capability.ts -> unknown -> full') }) it('selects cross-platform build and runtime lanes for the Notebook network sandbox', () => { const path = 'packages/notebook-network-sandbox/src/config.ts' const plan = classifyChanges([{ path, status: 'modified' }]) expect(plan.mode).toBe('selective') expect(plan.roots).toContain('notebook_network_sandbox') expect(plan.lanes).toEqual( expect.arrayContaining([ 'typecheck_node', 'unit_macos', 'linux_runtime', 'windows_runtime', 'windows_path', 'build' ]) ) expect(plan.bundles).toContain('linux_runtime') expect(plan.reasonChains).toContain(`${path} -> notebook_network_sandbox`) }) it('fails closed when a selected lane has no execution bundle', () => { const manifest = readManifest() delete manifest.laneBundles.policy expect(() => classifyChanges([], manifest)).toThrow( 'Missing execution bundle for selected lane: policy' ) }) it('fails closed when a selected lane references an undeclared execution bundle', () => { const manifest = readManifest() manifest.laneBundles.policy = 'undeclared' expect(() => classifyChanges([], manifest)).toThrow( 'Unknown execution bundle for selected lane policy: undeclared' ) }) it('keeps documentation-only changes on the stable minimal gate', () => { const plan = classifyChanges([ { path: 'docs/internal/pr-gate.md', status: 'modified' }, { path: 'README.md', status: 'modified' } ]) expect(plan.mode).toBe('selective') expect(plan.lanes).toEqual(['policy', 'docs']) expect(plan.bundles).toEqual(['policy', 'static']) expect(plan.reasonChains).toEqual( expect.arrayContaining([ 'README.md -> documentation', 'docs/internal/pr-gate.md -> documentation' ]) ) }) it.each(['yml', 'yaml'])('keeps issue-template .%s changes on static checks', (extension) => { const changes = [ { path: `.github/ISSUE_TEMPLATE/reproducibility_case.${extension}`, status: 'added' }, { path: '.github/ISSUE_TEMPLATE/config.yml', status: 'modified' }, { path: 'CONTRIBUTING.md', status: 'modified' }, { path: 'docs/reproducibility-cases/README.md', status: 'added' }, { path: 'docs/reproducibility-cases/index.md', status: 'added' } ] const plan = classifyChanges(changes) expect(plan.mode).toBe('selective') expect(plan.lanes).toEqual(['policy', 'docs', 'format']) expect(plan.bundles).toEqual(['policy', 'static']) for (const event of ['pull_request', 'merge_group']) { expect(platformExecutionPlan(plan, changes, event).bundles).toEqual(['policy', 'static']) } const tests = createAffectedTestPlan(changes, { status: 'unavailable-manifest-only', testFiles: [] }) expect(tests.mode).toBe('selective') expect(tests.testFiles).toEqual([]) }) it.each([ '.github/ISSUE_TEMPLATE/helper.js', '.github/ISSUE_TEMPLATE/nested/form.yml', '.github/unknown.yml', '.github/workflows/pr-gate.yml' ])('does not exempt unknown or executable inputs: %s', (path) => { expect(classifyChanges([{ path, status: 'added' }]).mode).toBe('full') }) it('retains the full fallback when an issue form accompanies runtime changes', () => { const plan = classifyChanges([ { path: '.github/ISSUE_TEMPLATE/bug_report.yml', status: 'modified' }, { path: 'src/unknown-runtime.ts', status: 'added' } ]) expect(plan.mode).toBe('full') expect(plan.bundles).toEqual(expect.arrayContaining(['unit', 'windows_core', 'windows_e2e'])) }) it('uses one specific owner instead of a broad fallback owner', () => { const manifest = readManifest() manifest.rules.push({ id: 'notebook_runtime', role: 'owner', paths: ['src/main/notebook/runtime-service.ts'], capabilities: ['notebook_runtime'] }) manifest.capabilities.notebook_runtime = { consumers: [], lanes: ['typecheck_node'] } const plan = classifyChanges( [{ path: 'src/main/notebook/runtime-service.ts', status: 'modified' }], manifest ) expect(plan.mode).toBe('selective') expect(plan.roots).toContain('notebook_runtime') expect(plan.roots).not.toContain('main_runtime') expect(plan.lanes).toEqual([ 'policy', 'typecheck_node', 'e2e_regressions_macos', 'e2e_delegation_macos' ]) }) it('keeps risk overlays additive after a specific owner replaces a fallback', () => { const manifest = readManifest() manifest.rules.push({ id: 'notebook_windows_runtime', role: 'owner', paths: ['src/main/notebook/windows-runtime.ts'], capabilities: ['notebook_runtime'] }) manifest.capabilities.notebook_runtime = { consumers: [], lanes: ['typecheck_node'] } const path = 'src/main/notebook/windows-runtime.ts' const plan = classifyChanges([{ path, status: 'modified' }], manifest) expect(plan.mode).toBe('selective') expect(plan.roots).toEqual( expect.arrayContaining(['notebook_windows_runtime', 'windows_sensitive']) ) expect(plan.roots).not.toContain('main_runtime') expect(plan.lanes).toEqual( expect.arrayContaining(['typecheck_node', 'windows_runtime', 'windows_path']) ) }) it('fails closed when multiple specific owners match one path', () => { const manifest = readManifest() for (const id of ['notebook_runtime_a', 'notebook_runtime_b']) { manifest.rules.push({ id, role: 'owner', paths: ['src/main/notebook/runtime-service.ts'], capabilities: ['main_runtime'] }) } const path = 'src/main/notebook/runtime-service.ts' const plan = classifyChanges([{ path, status: 'modified' }], manifest) expect(plan.mode).toBe('full') expect(plan.roots).toContain('owner_ambiguity') expect(plan.reasonChains).toContain( `${path} -> owner ambiguity: notebook_runtime_a, notebook_runtime_b -> full` ) }) it('fails closed when a matched path has no owner', () => { const manifest = readManifest() manifest.rules.push({ id: 'unowned_overlay', role: 'overlay', paths: ['src/new-runtime/overlay.ts'], capabilities: ['ci_integrity_surface'] }) const path = 'src/new-runtime/overlay.ts' const plan = classifyChanges([{ path, status: 'added' }], manifest) expect(plan.mode).toBe('full') expect(plan.roots).toContain('missing_owner') expect(plan.reasonChains).toContain(`${path} -> missing owner -> full`) }) it('treats Shared changes as cross-process consumer changes', () => { const plan = classifyChanges([{ path: 'src/shared/acp.ts', status: 'modified' }]) expect(plan.mode).toBe('selective') expect(plan.lanes).toEqual( expect.arrayContaining([ 'format', 'lint', 'typecheck_node', 'typecheck_web', 'interface_contracts', 'unit_macos', 'build', 'e2e_functional_macos', 'e2e_workspace_macos', 'e2e_accessibility_macos', 'e2e_visual_macos' ]) ) expect(plan.reasonChains).toContain( 'src/shared/acp.ts -> shared_contract -> preload_adapter -> renderer_settings -> e2e_workspace' ) }) it('expands Main runtime changes to desktop behavior consumers', () => { const plan = classifyChanges([ { path: 'src/main/notebook/runtime-service.ts', status: 'modified' } ]) expect(plan.mode).toBe('selective') expect(plan.lanes).toEqual( expect.arrayContaining([ 'format', 'lint', 'typecheck_node', 'typecheck_web', 'interface_contracts', 'unit_macos', 'build', 'e2e_functional_macos', 'e2e_workspace_macos' ]) ) expect(plan.lanes).not.toContain('e2e_visual_macos') expect(plan.reasonChains).toContain( 'src/main/notebook/runtime-service.ts -> main_runtime -> renderer_runtime -> e2e_workspace' ) }) it.each([ ['Windows runtime', 'src/main/windows.ts'], ['Shell search scope', 'src/main/notebook/shell-search-scope.ts'], ['PowerShell search parser', 'src/main/notebook/powershell-search-parser.ts'], ['PowerShell', 'src/main/notebook/micromamba-cache-powershell.test.ts'], ['path handling', 'src/main/acp/workspace-path.ts'], ['ACL behavior', 'src/main/notebook/micromamba-cache-acl.integration.test.ts'], ['Windows wheel recovery', 'src/main/notebook/pip-wheel-evidence.ts'], ['Windows wheel regression', 'src/main/notebook/pip-wheel-evidence.test.ts'], ['Windows install evidence', 'src/main/notebook/pip-install-evidence.test.ts'], ['storage', 'src/main/storage/ipc.ts'], ['session persistence', 'src/main/session-persistence/ipc.ts'], ['delegated process ownership', 'src/main/delegation/process-ownership.ts'], ['notebook shell process', 'src/main/notebook/shell-process.ts'], ['file save', 'src/main/file-save.ts'], ['specialist repository', 'src/main/specialist/repository.ts'], ['notebook runtime settings', 'src/main/settings/notebook-runtime-settings.ts'], ['preferences', 'src/main/settings/preferences.ts'], ['restricted runtime profile', 'src/main/acp/restricted-runtime-profile.ts'], ['CodeBuddy framework', 'src/main/agent-framework/codebuddy.ts'], ['CodeBuddy detect', 'src/main/settings/codebuddy-detect.ts'], ['managed CodeBuddy', 'src/main/settings/managed-codebuddy.ts'], ['immutable notebook inputs', 'src/main/immutable-input-authority.ts'], ['notebook package process sandbox', 'src/main/notebook/package-process-sandbox.ts'], ['WSL setup ownership', 'src/main/wsl/wsl-setup-owner.ts'], ['window shortcuts', 'src/main/window-shortcuts.ts'] ])('adds native Windows lanes for %s changes', (_category, path) => { const plan = classifyChanges([{ path, status: 'modified' }]) expect(plan.roots).toContain('windows_sensitive') expect(plan.lanes).toEqual(expect.arrayContaining(['windows_runtime', 'windows_path'])) expect(plan.reasonChains).toContain(`${path} -> windows_sensitive`) }) it('requires a live CDN bundle when the immutable runtime version changes', () => { const plan = classifyChanges([ { path: 'src/main/notebook/runtime-paths.ts', status: 'modified' } ]) expect(plan.roots).toEqual(expect.arrayContaining(['main_runtime', 'runtime_bundle'])) expect(plan.lanes).toContain('runtime_bundle') expect(plan.reasonChains).toContain('src/main/notebook/runtime-paths.ts -> runtime_bundle') }) it('does not add focused Windows lanes for platform-neutral Main changes', () => { const plan = classifyChanges([ { path: 'src/main/notebook/runtime-service.ts', status: 'modified' } ]) expect(plan.roots).not.toContain('windows_sensitive') expect(plan.lanes).not.toContain('windows_runtime') expect(plan.lanes).not.toContain('windows_path') }) it.each([ ['renderer view', 'src/renderer/src/components/Button.tsx'], ['renderer locale catalog', 'src/shared/i18n/locales/ja.json'], ['Korean locale catalog', 'src/shared/i18n/locales/ko.json'], ['French locale catalog', 'src/shared/i18n/locales/fr.json'], ['Spanish locale catalog', 'src/shared/i18n/locales/es.json'], ['German locale catalog', 'src/shared/i18n/locales/de.json'], ['shared contract', 'src/shared/acp.ts'], ['main runtime', 'src/main/notebook/runtime-service.ts'] ])('selects the i18n catalog lane for a scanned %s change', (_label, path) => { const plan = classifyChanges([{ path, status: 'modified' }]) expect(plan.lanes).toContain('i18n') expect(plan.bundles).toContain('static') }) it.each( readdirSync(resolve('src/shared/i18n/locales')) .filter((name) => name.endsWith('.json')) .map((name) => name.replace(/\.json$/u, ''))