fix: close release review gaps and expose failing CI suites

This commit is contained in:
haelyra
2026-09-07 17:57:26 -04:00
parent ba3a64a2c5
commit 14e731c6d5
12 changed files with 572 additions and 43 deletions
+8 -2
View File
@@ -255,6 +255,10 @@ function resolveManagedHookCommands(managedHooks, targetRoot) {
const encodedRoot = Buffer.from(targetRoot, 'utf8').toString('base64');
const rootExpression = `Buffer.from('${encodedRoot}','base64').toString('utf8')`;
const resolveCommand = command => {
// Leave invalid entries intact so managed-hook validation reports them.
if (typeof command !== 'string') {
return command;
}
const resolved = command
.split(PLUGIN_ROOT_ENV_PROLOGUE)
.join(`var e=${rootExpression};`);
@@ -273,9 +277,11 @@ function resolveManagedHookCommands(managedHooks, targetRoot) {
...entry,
hooks: entry.hooks.map(hook => ({
...hook,
...(typeof hook.command === 'string'
...(typeof hook.command === 'string' || Array.isArray(hook.command)
? {
command: resolveCommand(hook.command),
command: Array.isArray(hook.command)
? hook.command.map(resolveCommand)
: resolveCommand(hook.command),
}
: {}),
})),
+13 -4
View File
@@ -373,9 +373,12 @@ async function applyPreflightedManagedPlan(entry) {
: preflightManagedPlan(entry.preview.plan);
const ownedDestinations = new Set(preview.ownershipSnapshot.destinations);
let expectedStateFingerprint = preview.ownershipSnapshot.stateFingerprint;
const expectedOperations = new Map(preview.operations.map(operation => [
canonicalPath(operation.destinationPath), operation,
// Several ordered JSON merges may share one destination. Preserve each
// operation's preview instead of collapsing that sequence to one path entry.
const expectedOperations = new Map(preview.plan.operations.map((operation, index) => [
operation, preview.operations[index],
]));
const writtenDestinations = new Set();
const assertStateUnchanged = () => (
assertInstallStateUnchanged(preview.plan, expectedStateFingerprint)
);
@@ -385,12 +388,17 @@ async function applyPreflightedManagedPlan(entry) {
};
const assertOperationUnchanged = operation => {
const destination = canonicalPath(operation.destinationPath);
const expected = expectedOperations.get(destination);
const expected = expectedOperations.get(operation);
const currentClassification = classifyManagedOperation(operation, ownedDestinations);
const expectedClassification = operation.kind === 'merge-json'
&& writtenDestinations.has(destination)
? 'managed-json-update'
: expected && expected.classification;
if (
!expected
|| expected.kind !== operation.kind
|| expected.classification !== currentClassification
|| canonicalPath(expected.destinationPath) !== destination
|| expectedClassification !== currentClassification
) {
throw new Error(
`Refusing to write ${operation.destinationPath}: destination changed after Kimi preflight.`
@@ -412,6 +420,7 @@ async function applyPreflightedManagedPlan(entry) {
assertStateUnchanged();
const destination = assertOperationUnchanged(operation);
ownedDestinations.add(destination);
writtenDestinations.add(destination);
},
beforeInstallStateWrite: prepareInstallStateWrite,
});
+93 -27
View File
@@ -58,6 +58,21 @@ const START_PROCESS_SWITCH_PARAMETERS = new Set([
'usenewenvironment',
'wait',
]);
const ALIAS_VALUE_PARAMETERS = new Set([
'name', 'value', 'description', 'option', 'scope',
'erroraction', 'warningaction', 'informationaction', 'progressaction',
'errorvariable', 'warningvariable', 'informationvariable',
'outvariable', 'outbuffer', 'pipelinevariable',
]);
const ALIAS_SWITCH_PARAMETERS = new Set([
'force', 'passthru', 'whatif', 'confirm', 'verbose', 'debug',
]);
const ALIAS_PARAMETER_ABBREVIATIONS = Object.freeze({
ea: 'erroraction', wa: 'warningaction', infa: 'informationaction', proga: 'progressaction',
ev: 'errorvariable', wv: 'warningvariable', iv: 'informationvariable',
ov: 'outvariable', ob: 'outbuffer', pv: 'pipelinevariable',
wi: 'whatif', cf: 'confirm', vb: 'verbose', db: 'debug',
});
const MAX_SCAN_DEPTH = 4;
const MAX_CONTEXT_LENGTH = 4096;
const DYNAMIC_EXECUTION_MARKER = '__ecc_dynamic_execution__';
@@ -1225,16 +1240,22 @@ function addNestedScan(payload, depth, findings, analysis, options = {}, scanSta
scanPowerShell(payload, depth + 1, findings, analysis, options, scanState);
}
function staticPipelineInput(tokens) {
function staticTokenValue(tokens, index, state, findings, inline = false) {
const value = inline ? parameterValue(tokens[index]) : tokens[index];
const quoteKind = inline ? tokens.inlineValueQuoteKinds?.[index] : tokens.quoteKinds?.[index];
if (quoteKind === "'") return value;
const source = inline
? parameterValue(tokens.tokenSources?.[index] || tokens[index])
: tokens.tokenSources?.[index] ?? value;
return expandStaticDoubleQuotedString(source, state, findings);
}
function staticPipelineInput(tokens, state, findings) {
if (!tokens || tokens.length === 0) return null;
if (tokens.length === 1) {
const value = String(tokens[0] || '');
return value || null;
}
if (tokens.length === 1) return staticTokenValue(tokens, 0, state, findings);
const command = commandBasename(tokens[0]);
if ((command === 'write-output' || command === 'echo') && tokens.length === 2) {
const value = String(tokens[1] || '');
return tokens.quotedTokens?.[1] === true || /\s/.test(value) ? value : null;
return staticTokenValue(tokens, 1, state, findings);
}
return null;
}
@@ -1272,7 +1293,7 @@ function scanNestedPowerShell(tokens, depth, findings, analysis, scanState, upst
let payload = inlinePayload
? [inlinePayload, ...tokens.slice(index + 1)].join(' ')
: tokens.slice(index + 1).join(' ');
const pipelinePayload = payload === '-' ? staticPipelineInput(upstreamTokens) : null;
const pipelinePayload = payload === '-' ? staticPipelineInput(upstreamTokens, scanState, findings) : null;
const payloadIndex = index + 1;
const inlineQuoteKind = tokens.inlineValueQuoteKinds?.[index];
if (inlinePayload && inlineQuoteKind !== "'") {
@@ -1754,26 +1775,70 @@ function scanScriptBlockConsumer(tokens, quotedTokens, findings, state) {
}
}
function staticAliasDefinition(tokens, quotedTokens = []) {
let name = null;
let value = null;
function aliasParameterName(token) {
const name = String(token).replace(/^-+/, '').split(':')[0].toLowerCase();
if (Object.hasOwn(ALIAS_PARAMETER_ABBREVIATIONS, name)) return ALIAS_PARAMETER_ABBREVIATIONS[name];
const parameters = [...ALIAS_VALUE_PARAMETERS, ...ALIAS_SWITCH_PARAMETERS];
if (parameters.includes(name)) return name;
const matches = parameters.filter(parameter => name && parameter.startsWith(name));
return matches.length === 1 ? matches[0] : null;
}
function aliasArguments(tokens, quotedTokens) {
const named = new Map();
const positional = [];
let ambiguous = false;
for (let index = 1; index < tokens.length; index += 1) {
const token = tokens[index];
if (!quotedTokens[index] && isParameterPrefix(token, 'name')) {
name = parameterValue(token) || tokens[++index] || null;
} else if (!quotedTokens[index] && isParameterPrefix(token, 'value')) {
value = parameterValue(token) || tokens[++index] || null;
} else if (!String(token).startsWith('-')) {
positional.push(token);
const token = String(tokens[index]);
if (quotedTokens[index] || !token.startsWith('-')) {
positional.push({ index, inline: false });
if (!quotedTokens[index] && token.startsWith('@')) ambiguous = true;
continue;
}
const parameter = aliasParameterName(token);
if (!parameter) {
ambiguous = true;
continue;
}
if (named.has(parameter)) ambiguous = true;
const inline = token.includes(':');
const argument = { index: ALIAS_VALUE_PARAMETERS.has(parameter) && !inline ? ++index : index, inline };
if (ALIAS_VALUE_PARAMETERS.has(parameter) && tokens[argument.index] === undefined) ambiguous = true;
named.set(parameter, argument);
// Option accepts a comma-separated array; its continuation belongs to the
// named parameter rather than the remaining positional name/value slots.
if (parameter === 'option') {
while (index + 1 < tokens.length && !quotedTokens[index] &&
(String(tokens[index]).endsWith(',') || String(tokens[index + 1]).startsWith(','))) index += 1;
}
}
name ||= positional[0] || null;
value ||= positional[1] || null;
if (!/^[A-Za-z_][\w-]*$/.test(name || '') || !/^[A-Za-z_][\w./\\-]*$/.test(value || '')) {
return null;
}
return { name: name.toLowerCase(), value };
return { named, positional, ambiguous };
}
function staticAliasDefinitions(tokens, quotedTokens, state) {
const args = aliasArguments(tokens, quotedTokens);
// Definitions stay inert. Uncertain binding is gated only when a candidate
// alias is invoked, without allowing auxiliary values to hide its target.
const unresolved = new Set();
const resolve = argument => argument
? staticTokenValue(tokens, argument.index, state, unresolved, argument.inline)
: null;
let positionalIndex = 0;
const nameArgument = args.named.get('name') || args.positional[positionalIndex++];
const valueArgument = args.named.get('value') || args.positional[positionalIndex++];
const name = resolve(nameArgument);
const value = resolve(valueArgument);
const ambiguous = args.ambiguous || positionalIndex < args.positional.length;
const possibleNames = args.named.has('value') ? args.positional : args.positional.slice(0, -1);
const names = ambiguous && !args.named.has('name')
? [name, ...possibleNames.map(resolve)]
: [name];
const target = ambiguous || value === null || /^@/.test(value)
? DYNAMIC_EXECUTION_MARKER
: value;
if (!/^[A-Za-z_][\w./\\-]*$/.test(target || '')) return [];
return [...new Set(names.filter(candidate => /^[A-Za-z_][\w-]*$/.test(candidate || '')))]
.map(candidate => ({ name: candidate.toLowerCase(), value: target }));
}
function scanInvokeScriptCalls(source, unquoted, depth, findings, analysis, state) {
@@ -1862,9 +1927,10 @@ function scanPowerShell(command, depth, findings, analysis = null, options = {},
state
);
}
if (commandName === 'set-alias' || commandName === 'new-alias') {
const definition = staticAliasDefinition(tokens, executable.quotedTokens);
if (definition) state.aliases.set(definition.name, definition.value);
if (['set-alias', 'new-alias', 'sal', 'nal'].includes(commandName)) {
for (const definition of staticAliasDefinitions(tokens, executable.quotedTokens, state)) {
state.aliases.set(definition.name, definition.value);
}
}
const classInvocation = commandName.match(/^\[([a-z_][\w-]*)\]::/i);
if (classInvocation) recordInvocation(state, `__class__:${classInvocation[1].toLowerCase()}`);