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
@@ -130,7 +130,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
|
|||||||
```
|
```
|
||||||
agents/ — 13 specialized subagents
|
agents/ — 13 specialized subagents
|
||||||
skills/ — 65+ workflow skills and domain knowledge
|
skills/ — 65+ workflow skills and domain knowledge
|
||||||
commands/ — 33 slash commands
|
commands/ — 40 slash commands
|
||||||
hooks/ — Trigger-based automations
|
hooks/ — Trigger-based automations
|
||||||
rules/ — Always-follow guidelines (common + per-language)
|
rules/ — Always-follow guidelines (common + per-language)
|
||||||
scripts/ — Cross-platform Node.js utilities
|
scripts/ — Cross-platform Node.js utilities
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
"lint": "eslint . && markdownlint '**/*.md' --ignore node_modules",
|
"lint": "eslint . && markdownlint '**/*.md' --ignore node_modules",
|
||||||
"claw": "node scripts/claw.js",
|
"claw": "node scripts/claw.js",
|
||||||
"test": "node scripts/ci/validate-agents.js && node scripts/ci/validate-commands.js && node scripts/ci/validate-rules.js && node scripts/ci/validate-skills.js && node scripts/ci/validate-hooks.js && node scripts/ci/validate-no-personal-paths.js && node tests/run-all.js",
|
"test": "node scripts/ci/validate-agents.js && node scripts/ci/validate-commands.js && node scripts/ci/validate-rules.js && node scripts/ci/validate-skills.js && node scripts/ci/validate-hooks.js && node scripts/ci/validate-no-personal-paths.js && node tests/run-all.js",
|
||||||
"coverage": "c8 --all --include='scripts/**/*.js' --reporter=text --reporter=lcov node tests/run-all.js"
|
"coverage": "c8 --all --include=\"scripts/**/*.js\" --check-coverage --lines 80 --functions 80 --branches 80 --statements 80 --reporter=text --reporter=lcov node tests/run-all.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "Claude Code Hooks Configuration",
|
"title": "Claude Code Hooks Configuration",
|
||||||
"description": "Configuration for Claude Code hooks. Event types are validated at runtime and must be one of: PreToolUse, PostToolUse, PreCompact, SessionStart, SessionEnd, Stop, Notification, SubagentStop",
|
"description": "Configuration for Claude Code hooks. Supports current Claude Code hook events and hook action types.",
|
||||||
"$defs": {
|
"$defs": {
|
||||||
"hookItem": {
|
"stringArray": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"commandHookItem": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"type",
|
"type",
|
||||||
@@ -12,19 +20,17 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["command", "notification"],
|
"const": "command",
|
||||||
"description": "Hook action type (command or notification)"
|
"description": "Run a local command"
|
||||||
},
|
},
|
||||||
"command": {
|
"command": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "array",
|
"$ref": "#/$defs/stringArray"
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -37,17 +43,94 @@
|
|||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"description": "Timeout in seconds for async hooks"
|
"description": "Timeout in seconds for async hooks"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"httpHookItem": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "http"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allowedEnvVars": {
|
||||||
|
"$ref": "#/$defs/stringArray"
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"promptHookItem": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"prompt"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["prompt", "agent"]
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"hookItem": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/$defs/commandHookItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/$defs/httpHookItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/$defs/promptHookItem"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"matcherEntry": {
|
"matcherEntry": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"matcher",
|
|
||||||
"hooks"
|
"hooks"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"type": "string"
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -70,6 +153,28 @@
|
|||||||
},
|
},
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"enum": [
|
||||||
|
"SessionStart",
|
||||||
|
"UserPromptSubmit",
|
||||||
|
"PreToolUse",
|
||||||
|
"PermissionRequest",
|
||||||
|
"PostToolUse",
|
||||||
|
"PostToolUseFailure",
|
||||||
|
"Notification",
|
||||||
|
"SubagentStart",
|
||||||
|
"Stop",
|
||||||
|
"SubagentStop",
|
||||||
|
"PreCompact",
|
||||||
|
"InstructionsLoaded",
|
||||||
|
"TeammateIdle",
|
||||||
|
"TaskCompleted",
|
||||||
|
"ConfigChange",
|
||||||
|
"WorktreeCreate",
|
||||||
|
"WorktreeRemove",
|
||||||
|
"SessionEnd"
|
||||||
|
]
|
||||||
|
},
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
|||||||
@@ -17,27 +17,39 @@ const SKILLS_DIR = path.join(ROOT, 'skills');
|
|||||||
|
|
||||||
function listAgents() {
|
function listAgents() {
|
||||||
if (!fs.existsSync(AGENTS_DIR)) return [];
|
if (!fs.existsSync(AGENTS_DIR)) return [];
|
||||||
return fs.readdirSync(AGENTS_DIR)
|
try {
|
||||||
.filter(f => f.endsWith('.md'))
|
return fs.readdirSync(AGENTS_DIR)
|
||||||
.map(f => f.slice(0, -3))
|
.filter(f => f.endsWith('.md'))
|
||||||
.sort();
|
.map(f => f.slice(0, -3))
|
||||||
|
.sort();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to read agents directory (${AGENTS_DIR}): ${error.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function listCommands() {
|
function listCommands() {
|
||||||
if (!fs.existsSync(COMMANDS_DIR)) return [];
|
if (!fs.existsSync(COMMANDS_DIR)) return [];
|
||||||
return fs.readdirSync(COMMANDS_DIR)
|
try {
|
||||||
.filter(f => f.endsWith('.md'))
|
return fs.readdirSync(COMMANDS_DIR)
|
||||||
.map(f => f.slice(0, -3))
|
.filter(f => f.endsWith('.md'))
|
||||||
.sort();
|
.map(f => f.slice(0, -3))
|
||||||
|
.sort();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to read commands directory (${COMMANDS_DIR}): ${error.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function listSkills() {
|
function listSkills() {
|
||||||
if (!fs.existsSync(SKILLS_DIR)) return [];
|
if (!fs.existsSync(SKILLS_DIR)) return [];
|
||||||
const entries = fs.readdirSync(SKILLS_DIR, { withFileTypes: true });
|
try {
|
||||||
return entries
|
const entries = fs.readdirSync(SKILLS_DIR, { withFileTypes: true });
|
||||||
.filter(e => e.isDirectory() && fs.existsSync(path.join(SKILLS_DIR, e.name, 'SKILL.md')))
|
return entries
|
||||||
.map(e => e.name)
|
.filter(e => e.isDirectory() && fs.existsSync(path.join(SKILLS_DIR, e.name, 'SKILL.md')))
|
||||||
.sort();
|
.map(e => e.name)
|
||||||
|
.sort();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to read skills directory (${SKILLS_DIR}): ${error.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function run() {
|
function run() {
|
||||||
@@ -58,11 +70,11 @@ function run() {
|
|||||||
console.log(`- **Commands:** ${catalog.commands.count}`);
|
console.log(`- **Commands:** ${catalog.commands.count}`);
|
||||||
console.log(`- **Skills:** ${catalog.skills.count}\n`);
|
console.log(`- **Skills:** ${catalog.skills.count}\n`);
|
||||||
console.log('## Agents\n');
|
console.log('## Agents\n');
|
||||||
catalog.agents.list.forEach(a => console.log(`- ${a}`));
|
catalog.agents.list.forEach(a => { console.log(`- ${a}`); });
|
||||||
console.log('\n## Commands\n');
|
console.log('\n## Commands\n');
|
||||||
catalog.commands.list.forEach(c => console.log(`- ${c}`));
|
catalog.commands.list.forEach(c => { console.log(`- ${c}`); });
|
||||||
console.log('\n## Skills\n');
|
console.log('\n## Skills\n');
|
||||||
catalog.skills.list.forEach(s => console.log(`- ${s}`));
|
catalog.skills.list.forEach(s => { console.log(`- ${s}`); });
|
||||||
} else {
|
} else {
|
||||||
console.log(JSON.stringify(catalog, null, 2));
|
console.log(JSON.stringify(catalog, null, 2));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,36 @@ const Ajv = require('ajv');
|
|||||||
|
|
||||||
const HOOKS_FILE = path.join(__dirname, '../../hooks/hooks.json');
|
const HOOKS_FILE = path.join(__dirname, '../../hooks/hooks.json');
|
||||||
const HOOKS_SCHEMA_PATH = path.join(__dirname, '../../schemas/hooks.schema.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
|
* 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') {
|
if (!hook.type || typeof hook.type !== 'string') {
|
||||||
console.error(`ERROR: ${label} missing or invalid 'type' field`);
|
console.error(`ERROR: ${label} missing or invalid 'type' field`);
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
}
|
} else if (!VALID_HOOK_TYPES.includes(hook.type)) {
|
||||||
|
console.error(`ERROR: ${label} has unsupported hook type '${hook.type}'`);
|
||||||
// Validate optional async and timeout fields
|
|
||||||
if ('async' in hook && typeof hook.async !== 'boolean') {
|
|
||||||
console.error(`ERROR: ${label} 'async' must be a boolean`);
|
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('timeout' in hook && (typeof hook.timeout !== 'number' || hook.timeout < 0)) {
|
if ('timeout' in hook && (typeof hook.timeout !== 'number' || hook.timeout < 0)) {
|
||||||
console.error(`ERROR: ${label} 'timeout' must be a non-negative number`);
|
console.error(`ERROR: ${label} 'timeout' must be a non-negative number`);
|
||||||
hasErrors = true;
|
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)))) {
|
if (hook.type === 'command') {
|
||||||
console.error(`ERROR: ${label} missing or invalid 'command' field`);
|
if ('async' in hook && typeof hook.async !== 'boolean') {
|
||||||
hasErrors = true;
|
console.error(`ERROR: ${label} 'async' must be a boolean`);
|
||||||
} else if (typeof hook.command === 'string') {
|
hasErrors = true;
|
||||||
// Validate inline JS syntax in node -e commands
|
}
|
||||||
const nodeEMatch = hook.command.match(/^node -e "(.*)"$/s);
|
|
||||||
if (nodeEMatch) {
|
if (!isNonEmptyString(hook.command) && !isNonEmptyStringArray(hook.command)) {
|
||||||
try {
|
console.error(`ERROR: ${label} missing or invalid 'command' field`);
|
||||||
new vm.Script(nodeEMatch[1].replace(/\\\\/g, '\\').replace(/\\"/g, '"').replace(/\\n/g, '\n').replace(/\\t/g, '\t'));
|
hasErrors = true;
|
||||||
} catch (syntaxErr) {
|
} else if (typeof hook.command === 'string') {
|
||||||
console.error(`ERROR: ${label} has invalid inline JS: ${syntaxErr.message}`);
|
const nodeEMatch = hook.command.match(/^node -e "(.*)"$/s);
|
||||||
hasErrors = true;
|
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;
|
return hasErrors;
|
||||||
@@ -110,9 +179,12 @@ function validateHooks() {
|
|||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!matcher.matcher) {
|
if (!('matcher' in matcher) && !EVENTS_WITHOUT_MATCHER.has(eventType)) {
|
||||||
console.error(`ERROR: ${eventType}[${i}] missing 'matcher' field`);
|
console.error(`ERROR: ${eventType}[${i}] missing 'matcher' field`);
|
||||||
hasErrors = true;
|
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)) {
|
if (!matcher.hooks || !Array.isArray(matcher.hooks)) {
|
||||||
console.error(`ERROR: ${eventType}[${i}] missing 'hooks' array`);
|
console.error(`ERROR: ${eventType}[${i}] missing 'hooks' array`);
|
||||||
@@ -132,9 +204,12 @@ function validateHooks() {
|
|||||||
// Array format (legacy)
|
// Array format (legacy)
|
||||||
for (let i = 0; i < hooks.length; i++) {
|
for (let i = 0; i < hooks.length; i++) {
|
||||||
const hook = hooks[i];
|
const hook = hooks[i];
|
||||||
if (!hook.matcher) {
|
if (!('matcher' in hook)) {
|
||||||
console.error(`ERROR: Hook ${i} missing 'matcher' field`);
|
console.error(`ERROR: Hook ${i} missing 'matcher' field`);
|
||||||
hasErrors = true;
|
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)) {
|
if (!hook.hooks || !Array.isArray(hook.hooks)) {
|
||||||
console.error(`ERROR: Hook ${i} missing 'hooks' array`);
|
console.error(`ERROR: Hook ${i} missing 'hooks' array`);
|
||||||
|
|||||||
@@ -1927,7 +1927,7 @@ function runTests() {
|
|||||||
PreToolUse: [{
|
PreToolUse: [{
|
||||||
matcher: 'Write',
|
matcher: 'Write',
|
||||||
hooks: [{
|
hooks: [{
|
||||||
type: 'intercept',
|
type: 'command',
|
||||||
command: 'echo test',
|
command: 'echo test',
|
||||||
async: 'yes' // Should be boolean, not string
|
async: 'yes' // Should be boolean, not string
|
||||||
}]
|
}]
|
||||||
@@ -1947,7 +1947,7 @@ function runTests() {
|
|||||||
PostToolUse: [{
|
PostToolUse: [{
|
||||||
matcher: 'Edit',
|
matcher: 'Edit',
|
||||||
hooks: [{
|
hooks: [{
|
||||||
type: 'intercept',
|
type: 'command',
|
||||||
command: 'echo test',
|
command: 'echo test',
|
||||||
timeout: -5 // Must be non-negative
|
timeout: -5 // Must be non-negative
|
||||||
}]
|
}]
|
||||||
@@ -2105,6 +2105,31 @@ function runTests() {
|
|||||||
cleanupTestDir(testDir);
|
cleanupTestDir(testDir);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
console.log('\nRound 82b: validate-hooks (current official events and hook types):');
|
||||||
|
|
||||||
|
if (test('accepts UserPromptSubmit with omitted matcher and prompt/http/agent hooks', () => {
|
||||||
|
const testDir = createTestDir();
|
||||||
|
const hooksJson = JSON.stringify({
|
||||||
|
hooks: {
|
||||||
|
UserPromptSubmit: [
|
||||||
|
{
|
||||||
|
hooks: [
|
||||||
|
{ type: 'prompt', prompt: 'Summarize the request.' },
|
||||||
|
{ type: 'agent', prompt: 'Review for security issues.', model: 'gpt-5.4' },
|
||||||
|
{ type: 'http', url: 'https://example.com/hooks', headers: { Authorization: 'Bearer token' } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const hooksFile = path.join(testDir, 'hooks.json');
|
||||||
|
fs.writeFileSync(hooksFile, hooksJson);
|
||||||
|
|
||||||
|
const result = runValidatorWithDir('validate-hooks', 'HOOKS_FILE', hooksFile);
|
||||||
|
assert.strictEqual(result.code, 0, 'Should accept current official hook event/type combinations');
|
||||||
|
cleanupTestDir(testDir);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
// ── Round 83: validate-agents whitespace-only field, validate-skills empty SKILL.md ──
|
// ── Round 83: validate-agents whitespace-only field, validate-skills empty SKILL.md ──
|
||||||
|
|
||||||
console.log('\nRound 83: validate-agents (whitespace-only frontmatter field value):');
|
console.log('\nRound 83: validate-agents (whitespace-only frontmatter field value):');
|
||||||
|
|||||||
@@ -2561,6 +2561,7 @@ async function runTests() {
|
|||||||
assert.ok(!runAllSource.includes('execSync'), 'Should not use execSync');
|
assert.ok(!runAllSource.includes('execSync'), 'Should not use execSync');
|
||||||
// Verify it shows stderr
|
// Verify it shows stderr
|
||||||
assert.ok(runAllSource.includes('stderr'), 'Should handle stderr output');
|
assert.ok(runAllSource.includes('stderr'), 'Should handle stderr output');
|
||||||
|
assert.ok(runAllSource.includes('result.status !== 0'), 'Should treat non-zero child exits as failures');
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
// ── Round 32: post-edit-typecheck special characters & check-console-log ──
|
// ── Round 32: post-edit-typecheck special characters & check-console-log ──
|
||||||
|
|||||||
@@ -71,6 +71,17 @@ for (const testFile of testFiles) {
|
|||||||
|
|
||||||
if (passedMatch) totalPassed += parseInt(passedMatch[1], 10);
|
if (passedMatch) totalPassed += parseInt(passedMatch[1], 10);
|
||||||
if (failedMatch) totalFailed += parseInt(failedMatch[1], 10);
|
if (failedMatch) totalFailed += parseInt(failedMatch[1], 10);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
console.log(`✗ ${testFile} failed to start: ${result.error.message}`);
|
||||||
|
totalFailed += failedMatch ? 0 : 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.status !== 0) {
|
||||||
|
console.log(`✗ ${testFile} exited with status ${result.status}`);
|
||||||
|
totalFailed += failedMatch ? 0 : 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
totalTests = totalPassed + totalFailed;
|
totalTests = totalPassed + totalFailed;
|
||||||
|
|||||||
Reference in New Issue
Block a user