mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
fix: align architecture tooling with current hooks docs
This commit is contained in:
committed by
Affaan Mustafa
parent
a50349181a
commit
7705051910
@@ -10,7 +10,36 @@ const Ajv = require('ajv');
|
||||
|
||||
const HOOKS_FILE = path.join(__dirname, '../../hooks/hooks.json');
|
||||
const HOOKS_SCHEMA_PATH = path.join(__dirname, '../../schemas/hooks.schema.json');
|
||||
const VALID_EVENTS = ['PreToolUse', 'PostToolUse', 'PreCompact', 'SessionStart', 'SessionEnd', 'Stop', 'Notification', 'SubagentStop'];
|
||||
const VALID_EVENTS = [
|
||||
'SessionStart',
|
||||
'UserPromptSubmit',
|
||||
'PreToolUse',
|
||||
'PermissionRequest',
|
||||
'PostToolUse',
|
||||
'PostToolUseFailure',
|
||||
'Notification',
|
||||
'SubagentStart',
|
||||
'Stop',
|
||||
'SubagentStop',
|
||||
'PreCompact',
|
||||
'InstructionsLoaded',
|
||||
'TeammateIdle',
|
||||
'TaskCompleted',
|
||||
'ConfigChange',
|
||||
'WorktreeCreate',
|
||||
'WorktreeRemove',
|
||||
'SessionEnd',
|
||||
];
|
||||
const VALID_HOOK_TYPES = ['command', 'http', 'prompt', 'agent'];
|
||||
const EVENTS_WITHOUT_MATCHER = new Set(['UserPromptSubmit', 'Notification', 'Stop', 'SubagentStop']);
|
||||
|
||||
function isNonEmptyString(value) {
|
||||
return typeof value === 'string' && value.trim().length > 0;
|
||||
}
|
||||
|
||||
function isNonEmptyStringArray(value) {
|
||||
return Array.isArray(value) && value.length > 0 && value.every(item => isNonEmptyString(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a single hook entry has required fields and valid inline JS
|
||||
@@ -24,32 +53,72 @@ function validateHookEntry(hook, label) {
|
||||
if (!hook.type || typeof hook.type !== 'string') {
|
||||
console.error(`ERROR: ${label} missing or invalid 'type' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
// Validate optional async and timeout fields
|
||||
if ('async' in hook && typeof hook.async !== 'boolean') {
|
||||
console.error(`ERROR: ${label} 'async' must be a boolean`);
|
||||
} else if (!VALID_HOOK_TYPES.includes(hook.type)) {
|
||||
console.error(`ERROR: ${label} has unsupported hook type '${hook.type}'`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if ('timeout' in hook && (typeof hook.timeout !== 'number' || hook.timeout < 0)) {
|
||||
console.error(`ERROR: ${label} 'timeout' must be a non-negative number`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (!hook.command || (typeof hook.command !== 'string' && !Array.isArray(hook.command)) || (typeof hook.command === 'string' && !hook.command.trim()) || (Array.isArray(hook.command) && (hook.command.length === 0 || !hook.command.every(s => typeof s === 'string' && s.length > 0)))) {
|
||||
console.error(`ERROR: ${label} missing or invalid 'command' field`);
|
||||
hasErrors = true;
|
||||
} else if (typeof hook.command === 'string') {
|
||||
// Validate inline JS syntax in node -e commands
|
||||
const nodeEMatch = hook.command.match(/^node -e "(.*)"$/s);
|
||||
if (nodeEMatch) {
|
||||
try {
|
||||
new vm.Script(nodeEMatch[1].replace(/\\\\/g, '\\').replace(/\\"/g, '"').replace(/\\n/g, '\n').replace(/\\t/g, '\t'));
|
||||
} catch (syntaxErr) {
|
||||
console.error(`ERROR: ${label} has invalid inline JS: ${syntaxErr.message}`);
|
||||
hasErrors = true;
|
||||
if (hook.type === 'command') {
|
||||
if ('async' in hook && typeof hook.async !== 'boolean') {
|
||||
console.error(`ERROR: ${label} 'async' must be a boolean`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (!isNonEmptyString(hook.command) && !isNonEmptyStringArray(hook.command)) {
|
||||
console.error(`ERROR: ${label} missing or invalid 'command' field`);
|
||||
hasErrors = true;
|
||||
} else if (typeof hook.command === 'string') {
|
||||
const nodeEMatch = hook.command.match(/^node -e "(.*)"$/s);
|
||||
if (nodeEMatch) {
|
||||
try {
|
||||
new vm.Script(nodeEMatch[1].replace(/\\\\/g, '\\').replace(/\\"/g, '"').replace(/\\n/g, '\n').replace(/\\t/g, '\t'));
|
||||
} catch (syntaxErr) {
|
||||
console.error(`ERROR: ${label} has invalid inline JS: ${syntaxErr.message}`);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasErrors;
|
||||
}
|
||||
|
||||
if ('async' in hook) {
|
||||
console.error(`ERROR: ${label} 'async' is only supported for command hooks`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (hook.type === 'http') {
|
||||
if (!isNonEmptyString(hook.url)) {
|
||||
console.error(`ERROR: ${label} missing or invalid 'url' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if ('headers' in hook && (typeof hook.headers !== 'object' || hook.headers === null || Array.isArray(hook.headers) || !Object.values(hook.headers).every(value => typeof value === 'string'))) {
|
||||
console.error(`ERROR: ${label} 'headers' must be an object with string values`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if ('allowedEnvVars' in hook && (!Array.isArray(hook.allowedEnvVars) || !hook.allowedEnvVars.every(value => isNonEmptyString(value)))) {
|
||||
console.error(`ERROR: ${label} 'allowedEnvVars' must be an array of strings`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
return hasErrors;
|
||||
}
|
||||
|
||||
if (!isNonEmptyString(hook.prompt)) {
|
||||
console.error(`ERROR: ${label} missing or invalid 'prompt' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if ('model' in hook && !isNonEmptyString(hook.model)) {
|
||||
console.error(`ERROR: ${label} 'model' must be a non-empty string`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
return hasErrors;
|
||||
@@ -110,9 +179,12 @@ function validateHooks() {
|
||||
hasErrors = true;
|
||||
continue;
|
||||
}
|
||||
if (!matcher.matcher) {
|
||||
if (!('matcher' in matcher) && !EVENTS_WITHOUT_MATCHER.has(eventType)) {
|
||||
console.error(`ERROR: ${eventType}[${i}] missing 'matcher' field`);
|
||||
hasErrors = true;
|
||||
} else if ('matcher' in matcher && typeof matcher.matcher !== 'string' && (typeof matcher.matcher !== 'object' || matcher.matcher === null)) {
|
||||
console.error(`ERROR: ${eventType}[${i}] has invalid 'matcher' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
if (!matcher.hooks || !Array.isArray(matcher.hooks)) {
|
||||
console.error(`ERROR: ${eventType}[${i}] missing 'hooks' array`);
|
||||
@@ -132,9 +204,12 @@ function validateHooks() {
|
||||
// Array format (legacy)
|
||||
for (let i = 0; i < hooks.length; i++) {
|
||||
const hook = hooks[i];
|
||||
if (!hook.matcher) {
|
||||
if (!('matcher' in hook)) {
|
||||
console.error(`ERROR: Hook ${i} missing 'matcher' field`);
|
||||
hasErrors = true;
|
||||
} else if (typeof hook.matcher !== 'string' && (typeof hook.matcher !== 'object' || hook.matcher === null)) {
|
||||
console.error(`ERROR: Hook ${i} has invalid 'matcher' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
if (!hook.hooks || !Array.isArray(hook.hooks)) {
|
||||
console.error(`ERROR: Hook ${i} missing 'hooks' array`);
|
||||
|
||||
Reference in New Issue
Block a user