This commit was merged in pull request #842.
This commit is contained in:
@@ -601,15 +601,23 @@ describe('ensureClaudexMutatorGateSettings', () => {
|
||||
|
||||
const settings = JSON.parse(readFileSync(join(root, 'settings.json'), 'utf8')) as {
|
||||
preserved: boolean;
|
||||
hooks: { PreToolUse: Array<{ hooks: Array<{ command: string }> }> };
|
||||
hooks: Record<string, Array<{ matcher: string; hooks: Array<{ command: string }> }>>;
|
||||
};
|
||||
const commands = settings.hooks.PreToolUse.flatMap((entry) =>
|
||||
const commands = settings.hooks['PreToolUse']!.flatMap((entry) =>
|
||||
entry.hooks.map((hook) => hook.command),
|
||||
);
|
||||
expect(settings.preserved).toBe(true);
|
||||
expect(commands).toContain(
|
||||
'python3 ~/.config/mosaic/tools/lease-broker/mutator-gate.py --runtime claude',
|
||||
);
|
||||
expect(
|
||||
settings.hooks['PreCompact']?.some((entry) =>
|
||||
entry.hooks.some((hook) => hook.command.includes('revoke-lease.py')),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(settings.hooks['SessionStart']?.map((entry) => entry.matcher)).toEqual(
|
||||
expect.arrayContaining(['compact', 'resume|clear']),
|
||||
);
|
||||
expect(lstatSync(join(root, 'settings.json')).mode & 0o777).toBe(0o600);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
|
||||
@@ -287,17 +287,19 @@ export function buildClaudexEnv(
|
||||
|
||||
const CLAUDEX_MUTATOR_GATE_COMMAND =
|
||||
'python3 ~/.config/mosaic/tools/lease-broker/mutator-gate.py --runtime claude';
|
||||
const CLAUDEX_PRE_COMPACT_COMMAND =
|
||||
'python3 ~/.config/mosaic/tools/lease-broker/revoke-lease.py --runtime claude --reason pre-compact';
|
||||
const CLAUDEX_POST_COMPACT_COMMAND =
|
||||
'python3 ~/.config/mosaic/tools/lease-broker/revoke-lease.py --runtime claude --reason session-start-compact';
|
||||
const CLAUDEX_ROLLOVER_COMMAND =
|
||||
'python3 ~/.config/mosaic/tools/lease-broker/revoke-lease.py --runtime claude --reason session-start-rollover --bump-generation';
|
||||
|
||||
const CLAUDEX_MUTATOR_GATE_HOOK = {
|
||||
matcher: '.*',
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: CLAUDEX_MUTATOR_GATE_COMMAND,
|
||||
timeout: 3,
|
||||
},
|
||||
],
|
||||
};
|
||||
const CLAUDEX_MANDATORY_LEASE_HOOKS = [
|
||||
{ event: 'PreToolUse', matcher: '.*', command: CLAUDEX_MUTATOR_GATE_COMMAND },
|
||||
{ event: 'PreCompact', matcher: '.*', command: CLAUDEX_PRE_COMPACT_COMMAND },
|
||||
{ event: 'SessionStart', matcher: 'compact', command: CLAUDEX_POST_COMPACT_COMMAND },
|
||||
{ event: 'SessionStart', matcher: 'resume|clear', command: CLAUDEX_ROLLOVER_COMMAND },
|
||||
] as const;
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
@@ -331,26 +333,31 @@ export function ensureClaudexMutatorGateSettings(configDir: string): void {
|
||||
throw new Error('claudex: isolated settings hooks must be an object (fail closed).');
|
||||
}
|
||||
const hooks = hooksValue ?? {};
|
||||
const preToolUseValue = hooks['PreToolUse'];
|
||||
if (preToolUseValue !== undefined && !Array.isArray(preToolUseValue)) {
|
||||
throw new Error('claudex: isolated PreToolUse hooks must be an array (fail closed).');
|
||||
for (const mandatory of CLAUDEX_MANDATORY_LEASE_HOOKS) {
|
||||
const eventValue = hooks[mandatory.event];
|
||||
if (eventValue !== undefined && !Array.isArray(eventValue)) {
|
||||
throw new Error(`claudex: isolated ${mandatory.event} hooks must be an array (fail closed).`);
|
||||
}
|
||||
const eventHooks = eventValue ?? [];
|
||||
const hookPresent = eventHooks.some(
|
||||
(entry) =>
|
||||
isRecord(entry) &&
|
||||
entry['matcher'] === mandatory.matcher &&
|
||||
Array.isArray(entry['hooks']) &&
|
||||
entry['hooks'].some(
|
||||
(hook) =>
|
||||
isRecord(hook) && hook['type'] === 'command' && hook['command'] === mandatory.command,
|
||||
),
|
||||
);
|
||||
if (!hookPresent) {
|
||||
eventHooks.unshift({
|
||||
matcher: mandatory.matcher,
|
||||
hooks: [{ type: 'command', command: mandatory.command, timeout: 3 }],
|
||||
});
|
||||
}
|
||||
hooks[mandatory.event] = eventHooks;
|
||||
}
|
||||
const preToolUse = preToolUseValue ?? [];
|
||||
const gatePresent = preToolUse.some(
|
||||
(entry) =>
|
||||
isRecord(entry) &&
|
||||
entry['matcher'] === '.*' &&
|
||||
Array.isArray(entry['hooks']) &&
|
||||
entry['hooks'].some(
|
||||
(hook) =>
|
||||
isRecord(hook) &&
|
||||
hook['type'] === 'command' &&
|
||||
hook['command'] === CLAUDEX_MUTATOR_GATE_COMMAND,
|
||||
),
|
||||
);
|
||||
if (!gatePresent) preToolUse.unshift(CLAUDEX_MUTATOR_GATE_HOOK);
|
||||
|
||||
hooks['PreToolUse'] = preToolUse;
|
||||
settings['hooks'] = hooks;
|
||||
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, { mode: 0o600 });
|
||||
chmodSync(settingsPath, 0o600);
|
||||
|
||||
Reference in New Issue
Block a user