export const CORE_SCENARIO_IDS = Object.freeze([ 'REL-01', 'REL-02', 'REL-04', 'REL-05', 'REL-06', 'REL-09', 'REL-11', 'AUTH-01', 'AUTH-05', 'CHAT-01', 'CHAT-06', 'CHAT-07', 'AGENT-01', 'AGENT-03', 'MS-01', 'COMP-09', ]); const CRITICAL_IMPACT_RULES = Object.freeze([ { groups: ['AGENT', 'CFG', 'UI'], pattern: /^(?:(?:package|npm-shrinkwrap).*\.json|pnpm-lock\.yaml|yarn\.lock)$/i, }, { groups: ['AGENT', 'AUTH', 'CFG', 'CHAT', 'DATA'], pattern: /^(?:server\.mjs|schema\.sql|(?:migrations?|database)\/)/i, }, { groups: ['CFG', 'REL'], pattern: /^(?:release-gate\/|scripts\/(?:build-portal-runtime|check-release-ready|release-|run-release-gate|verify-release-gate|verify-canary-))/i, }, { groups: ['AGENT', 'CFG', 'REL'], pattern: /^scripts\/(?:run-memind-portal-prod|run-memind-portal-candidate|goosed.*compose)/i, }, { groups: ['CFG', 'REL'], pattern: /^(?:docs\/(?:production-release-guardian|release-gate-automation|release-canary-103)\.md|(?:PRODUCTION_RELEASE_RULES|ENGINEERING_WORKFLOW_RULES)\.md|\.github\/workflows\/)/i, }, { groups: ['AGENT', 'AUTH', 'CFG', 'CHAT', 'WX'], pattern: /^server\/portal-/i, }, { groups: ['AUTH', 'CFG', 'UI'], pattern: /^src\/(?:api\/core|config)\.[cm]?[jt]sx?$/i, }, { groups: ['AGENT', 'CFG', 'CHAT'], pattern: /^(?:tkmind-proxy|session-stream|session-stream-store|deepseek-no-think-proxy)\.mjs$/i, }, ]); const NON_RUNTIME_PATHS = Object.freeze([ /^(?:AGENTS|README|CHANGELOG)\.md$/i, /^\.runtime\//i, /^docs\//i, /^\.cursor\//i, /^\.codex\//i, /^scripts\/dev(?:-|\.|\/)/i, /^scripts\/.*(?:test|fixture|mock).*\.[cm]?[jt]s$/i, /\.(?:test|spec)\.[cm]?[jt]sx?$/i, /^(?:eslint|prettier|tsconfig|components)\..*$/i, /^openapi\.json$/i, ]); const IMPACT_RULES = Object.freeze([ { groups: ['MEM'], pattern: /(?:^|\/)(?:memory|episodic)[^/]*|memory-v2/i }, { groups: ['DATA'], pattern: /(?:page-data|dataset|page-policy)/i }, { groups: ['WX'], pattern: /(?:wechat|weixin|wx-)/i }, { groups: ['BILL'], pattern: /(?:billing|payment|charge|balance|subscription)/i }, { groups: ['PLAZA'], pattern: /(?:^|\/)plaza/i }, { groups: ['SCHED'], pattern: /(?:schedule|scheduler|reminder|cron)/i }, { groups: ['SEARCH'], pattern: /(?:search|weather|market|news-provider)/i }, { groups: ['XLS'], pattern: /(?:excel|xlsx|spreadsheet)/i }, { groups: ['IMGPG'], pattern: /(?:image|thumbnail|cover|imgproxy)/i }, { groups: ['FILE'], pattern: /(?:file|attachment|upload|document|pdf|docx|csv)/i }, { groups: ['MS'], pattern: /mindspace/i }, { groups: ['PAGE'], pattern: /(?:public-(?:page|finish)|published-page|publication|page-delivery|mindspace-public)/i }, { groups: ['AGENT'], pattern: /(?:agent|capabilit|goosed|worker|aider|mcp)/i }, { groups: ['CHAT'], pattern: /(?:chat|conversation|message|reply|session|sse|routing|intent)/i }, { groups: ['AUTH'], pattern: /(?:auth|access-policy|account|user-permission)/i }, { groups: ['CFG'], pattern: /(?:config|provider|model-catalog|orchestrator|analytics|disclosure)/i }, { groups: ['CFG'], pattern: /(?:^|\/)\.env(?:\.example)?$/i }, { groups: ['UI'], pattern: /^(?:src\/|public\/)|\.(?:css|scss|tsx|vue)$/i }, ]); const GROUP_DEPENDENCIES = Object.freeze({ MEM: ['CHAT'], DATA: ['AUTH', 'PAGE', 'MS'], WX: ['AUTH', 'CHAT'], BILL: ['AUTH', 'CHAT'], PLAZA: ['AUTH'], SEARCH: ['CHAT'], XLS: ['FILE'], IMGPG: ['FILE', 'PAGE'], FILE: ['AUTH', 'CHAT'], MS: ['PAGE'], PAGE: ['FILE', 'MS'], AGENT: ['CHAT', 'CFG'], }); function normalizePaths(paths) { return [...new Set(paths.map((item) => String(item).replaceAll('\\', '/')).filter(Boolean))].sort(); } function matchesAny(patterns, relativePath) { return patterns.some((pattern) => pattern.test(relativePath)); } function closeGroupDependencies(initialGroups) { const groups = new Set(initialGroups); const pending = [...groups]; while (pending.length > 0) { const group = pending.shift(); for (const dependency of GROUP_DEPENDENCIES[group] ?? []) { if (groups.has(dependency)) continue; groups.add(dependency); pending.push(dependency); } } return [...groups].sort(); } export function selectImpactScenarios({ catalog, changedPaths, blockingReasons = [], }) { const normalizedPaths = normalizePaths(changedPaths); const catalogIds = new Set(catalog.map((scenario) => scenario.id)); const missingCore = CORE_SCENARIO_IDS.filter((id) => !catalogIds.has(id)); if (missingCore.length > 0) { throw new Error(`Core release scenarios are missing from the catalog: ${missingCore.join(',')}`); } if (blockingReasons.length > 0) { throw new Error(`Impact selection is blocked: ${blockingReasons.join(',')}`); } const directGroups = new Set(); const unmappedPaths = []; for (const relativePath of normalizedPaths) { let matched = false; for (const rule of CRITICAL_IMPACT_RULES) { if (!rule.pattern.test(relativePath)) continue; matched = true; for (const group of rule.groups) directGroups.add(group); } if (matchesAny(NON_RUNTIME_PATHS, relativePath)) continue; for (const rule of IMPACT_RULES) { if (!rule.pattern.test(relativePath)) continue; matched = true; for (const group of rule.groups) directGroups.add(group); } if (matched) continue; unmappedPaths.push(relativePath); } if (unmappedPaths.length > 0) { throw new Error( `Impact selection has unmapped runtime paths: ${unmappedPaths.join(',')}`, ); } const impactGroups = closeGroupDependencies(directGroups); const strategy = impactGroups.length > 0 ? 'impact' : 'core'; const selected = new Set(CORE_SCENARIO_IDS); for (const scenario of catalog) { if (impactGroups.includes(scenario.group)) selected.add(scenario.id); } const selectedIds = catalog .map((scenario) => scenario.id) .filter((id) => selected.has(id)); return { policy_version: 2, strategy, catalog_total: catalog.length, core_ids: [...CORE_SCENARIO_IDS], impact_groups: impactGroups, changed_paths: normalizedPaths, unmapped_paths: unmappedPaths, full_gate_reasons: [], selected_ids: selectedIds, selected_total: selectedIds.length, }; }