Files
memind/release-gate/impact.mjs
T
john 6acd79e4fa
Memind CI / Test, build, and release guards (push) Failing after 3s
Fix MindSpace deliverable link redaction and release gate impact mapping.
Preserve local MindSpace URLs in assistant replies during architecture term redaction, and map db.mjs and skills-registry.mjs for impact selection.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 08:22:54 +08:00

193 lines
6.2 KiB
JavaScript

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|db\.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|skills-registry)/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,
};
}