@pcircle/memesh 2.8.11 โ 2.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -661
- package/README.de.md +171 -0
- package/README.es.md +171 -0
- package/README.fr.md +171 -0
- package/README.id.md +171 -0
- package/README.ja.md +171 -0
- package/README.ko.md +171 -0
- package/README.md +73 -100
- package/README.th.md +171 -0
- package/README.vi.md +171 -0
- package/README.zh-CN.md +171 -0
- package/README.zh-TW.md +71 -98
- package/dist/knowledge-graph/index.d.ts +22 -1
- package/dist/knowledge-graph/index.d.ts.map +1 -1
- package/dist/knowledge-graph/index.js +144 -3
- package/dist/knowledge-graph/index.js.map +1 -1
- package/dist/mcp/ServerInitializer.d.ts.map +1 -1
- package/dist/mcp/ServerInitializer.js +1 -1
- package/dist/mcp/ServerInitializer.js.map +1 -1
- package/dist/mcp/ToolDefinitions.d.ts.map +1 -1
- package/dist/mcp/ToolDefinitions.js +47 -55
- package/dist/mcp/ToolDefinitions.js.map +1 -1
- package/dist/mcp/ToolRouter.d.ts.map +1 -1
- package/dist/mcp/ToolRouter.js +4 -4
- package/dist/mcp/ToolRouter.js.map +1 -1
- package/dist/mcp/daemon/StdioProxyClient.d.ts.map +1 -1
- package/dist/mcp/daemon/StdioProxyClient.js +9 -1
- package/dist/mcp/daemon/StdioProxyClient.js.map +1 -1
- package/dist/mcp/handlers/BuddyHandlers.d.ts +3 -1
- package/dist/mcp/handlers/BuddyHandlers.d.ts.map +1 -1
- package/dist/mcp/handlers/BuddyHandlers.js +6 -5
- package/dist/mcp/handlers/BuddyHandlers.js.map +1 -1
- package/dist/mcp/handlers/ToolHandlers.d.ts.map +1 -1
- package/dist/mcp/handlers/ToolHandlers.js +1 -2
- package/dist/mcp/handlers/ToolHandlers.js.map +1 -1
- package/dist/mcp/resources/quick-reference.md +1 -1
- package/dist/mcp/schemas/OutputSchemas.d.ts +116 -53
- package/dist/mcp/schemas/OutputSchemas.d.ts.map +1 -1
- package/dist/mcp/schemas/OutputSchemas.js +64 -26
- package/dist/mcp/schemas/OutputSchemas.js.map +1 -1
- package/dist/mcp/server-bootstrap.js +89 -9
- package/dist/mcp/server-bootstrap.js.map +1 -1
- package/dist/mcp/tools/buddy-do.d.ts +2 -1
- package/dist/mcp/tools/buddy-do.d.ts.map +1 -1
- package/dist/mcp/tools/buddy-do.js +91 -4
- package/dist/mcp/tools/buddy-do.js.map +1 -1
- package/dist/mcp/tools/buddy-remember.d.ts +0 -5
- package/dist/mcp/tools/buddy-remember.d.ts.map +1 -1
- package/dist/mcp/tools/buddy-remember.js.map +1 -1
- package/dist/mcp/tools/memesh-agent-register.d.ts +20 -0
- package/dist/mcp/tools/memesh-agent-register.d.ts.map +1 -0
- package/dist/mcp/tools/memesh-agent-register.js +80 -0
- package/dist/mcp/tools/memesh-agent-register.js.map +1 -0
- package/dist/mcp/tools/memesh-cloud-sync.js +27 -8
- package/dist/mcp/tools/memesh-cloud-sync.js.map +1 -1
- package/dist/mcp/tools/memesh-metrics.d.ts +13 -0
- package/dist/mcp/tools/memesh-metrics.d.ts.map +1 -0
- package/dist/mcp/tools/memesh-metrics.js +193 -0
- package/dist/mcp/tools/memesh-metrics.js.map +1 -0
- package/dist/memory/UnifiedMemoryStore.d.ts +1 -1
- package/dist/memory/UnifiedMemoryStore.d.ts.map +1 -1
- package/dist/memory/UnifiedMemoryStore.js +4 -3
- package/dist/memory/UnifiedMemoryStore.js.map +1 -1
- package/package.json +9 -12
- package/plugin.json +2 -2
- package/scripts/hooks/README.md +230 -0
- package/scripts/hooks/__tests__/hook-test-harness.js +218 -0
- package/scripts/hooks/__tests__/hooks.test.js +267 -0
- package/scripts/hooks/hook-utils.js +899 -0
- package/scripts/hooks/post-commit.js +307 -0
- package/scripts/hooks/post-tool-use.js +812 -0
- package/scripts/hooks/pre-tool-use.js +462 -0
- package/scripts/hooks/session-start.js +544 -0
- package/scripts/hooks/stop.js +673 -0
- package/scripts/hooks/subagent-stop.js +184 -0
- package/scripts/hooks/templates/planning-template.md +46 -0
- package/scripts/postinstall-lib.js +8 -4
- package/scripts/postinstall-new.js +110 -7
- package/scripts/skills/comprehensive-code-review/SKILL.md +276 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook Test Harness โ Simulates Claude Code hook execution.
|
|
5
|
+
*
|
|
6
|
+
* Pipes mock stdin JSON to a hook script and validates the output.
|
|
7
|
+
* Does NOT require Claude Code runtime.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node hook-test-harness.js <hook-script> <mock-stdin-json>
|
|
11
|
+
* node hook-test-harness.js ../pre-tool-use.js '{"tool_name":"Bash","tool_input":{"command":"git commit -m test"}}'
|
|
12
|
+
*
|
|
13
|
+
* Or programmatically:
|
|
14
|
+
* import { runHook, assertJSON, assertContains } from './hook-test-harness.js';
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { execFile } from 'child_process';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = path.dirname(__filename);
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Core Test Functions
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Run a hook script with mock stdin and capture output.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} hookPath - Path to hook script (relative to hooks/ dir or absolute)
|
|
32
|
+
* @param {Object|string} stdinData - JSON data to pipe as stdin
|
|
33
|
+
* @param {Object} options - Options
|
|
34
|
+
* @param {number} options.timeout - Timeout in ms (default: 10000)
|
|
35
|
+
* @param {Object} options.env - Additional environment variables
|
|
36
|
+
* @returns {Promise<{ stdout: string, stderr: string, exitCode: number, parsed: Object|null }>}
|
|
37
|
+
*/
|
|
38
|
+
export function runHook(hookPath, stdinData, options = {}) {
|
|
39
|
+
const { timeout = 10000, env = {} } = options;
|
|
40
|
+
|
|
41
|
+
// Resolve hook path relative to hooks directory
|
|
42
|
+
const resolvedPath = path.isAbsolute(hookPath)
|
|
43
|
+
? hookPath
|
|
44
|
+
: path.resolve(__dirname, '..', hookPath);
|
|
45
|
+
|
|
46
|
+
const stdinStr = typeof stdinData === 'string'
|
|
47
|
+
? stdinData
|
|
48
|
+
: JSON.stringify(stdinData);
|
|
49
|
+
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const child = execFile('node', [resolvedPath], {
|
|
52
|
+
encoding: 'utf-8',
|
|
53
|
+
timeout,
|
|
54
|
+
env: { ...process.env, ...env },
|
|
55
|
+
}, (error, stdout, stderr) => {
|
|
56
|
+
let parsed = null;
|
|
57
|
+
try {
|
|
58
|
+
if (stdout.trim()) {
|
|
59
|
+
parsed = JSON.parse(stdout.trim());
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// Not JSON output โ that's fine for some hooks
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
resolve({
|
|
66
|
+
stdout: stdout || '',
|
|
67
|
+
stderr: stderr || '',
|
|
68
|
+
exitCode: error ? (error.code || 1) : 0,
|
|
69
|
+
parsed,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Pipe stdin
|
|
74
|
+
if (child.stdin) {
|
|
75
|
+
child.stdin.write(stdinStr);
|
|
76
|
+
child.stdin.end();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// Assertion Helpers
|
|
83
|
+
// ============================================================================
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Assert that the hook output is valid JSON with expected structure.
|
|
87
|
+
* @param {Object} result - Result from runHook()
|
|
88
|
+
* @param {string} hookEventName - Expected hookEventName
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
export function assertHookResponse(result, hookEventName) {
|
|
92
|
+
if (!result.parsed) {
|
|
93
|
+
console.error(` FAIL: No JSON output`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const output = result.parsed.hookSpecificOutput;
|
|
98
|
+
if (!output) {
|
|
99
|
+
console.error(` FAIL: Missing hookSpecificOutput`);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (output.hookEventName !== hookEventName) {
|
|
104
|
+
console.error(` FAIL: hookEventName is "${output.hookEventName}", expected "${hookEventName}"`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Assert that stdout contains a substring.
|
|
113
|
+
* @param {Object} result - Result from runHook()
|
|
114
|
+
* @param {string} substring - Expected substring
|
|
115
|
+
* @returns {boolean}
|
|
116
|
+
*/
|
|
117
|
+
export function assertContains(result, substring) {
|
|
118
|
+
const fullOutput = result.stdout + result.stderr;
|
|
119
|
+
if (!fullOutput.includes(substring)) {
|
|
120
|
+
console.error(` FAIL: Output does not contain "${substring}"`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Assert hook exited silently (no stdout, exit 0).
|
|
128
|
+
* @param {Object} result - Result from runHook()
|
|
129
|
+
* @returns {boolean}
|
|
130
|
+
*/
|
|
131
|
+
export function assertSilent(result) {
|
|
132
|
+
if (result.stdout.trim() !== '') {
|
|
133
|
+
console.error(` FAIL: Expected silent exit, got stdout: ${result.stdout.substring(0, 100)}`);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// Test Runner
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Simple test runner for hook tests.
|
|
145
|
+
* @param {string} suiteName - Test suite name
|
|
146
|
+
* @param {Array<{name: string, fn: Function}>} tests - Test cases
|
|
147
|
+
*/
|
|
148
|
+
export async function runTests(suiteName, tests) {
|
|
149
|
+
console.log(`\n ${suiteName}`);
|
|
150
|
+
console.log(' ' + 'โ'.repeat(50));
|
|
151
|
+
|
|
152
|
+
let passed = 0;
|
|
153
|
+
let failed = 0;
|
|
154
|
+
|
|
155
|
+
for (const test of tests) {
|
|
156
|
+
try {
|
|
157
|
+
const result = await test.fn();
|
|
158
|
+
if (result !== false) {
|
|
159
|
+
console.log(` โ
${test.name}`);
|
|
160
|
+
passed++;
|
|
161
|
+
} else {
|
|
162
|
+
console.log(` โ ${test.name}`);
|
|
163
|
+
failed++;
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.log(` โ ${test.name}`);
|
|
167
|
+
console.error(` Error: ${error.message}`);
|
|
168
|
+
failed++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log(' ' + 'โ'.repeat(50));
|
|
173
|
+
console.log(` Results: ${passed} passed, ${failed} failed\n`);
|
|
174
|
+
|
|
175
|
+
return { passed, failed };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// CLI Mode
|
|
180
|
+
// ============================================================================
|
|
181
|
+
|
|
182
|
+
async function main() {
|
|
183
|
+
const args = process.argv.slice(2);
|
|
184
|
+
|
|
185
|
+
if (args.length < 2) {
|
|
186
|
+
console.log('Usage: node hook-test-harness.js <hook-script> <mock-stdin-json>');
|
|
187
|
+
console.log('');
|
|
188
|
+
console.log('Examples:');
|
|
189
|
+
console.log(' node hook-test-harness.js pre-tool-use.js \'{"tool_name":"Bash","tool_input":{"command":"git commit -m test"}}\'');
|
|
190
|
+
console.log(' node hook-test-harness.js post-tool-use.js \'{"tool_name":"Read","tool_input":{"file_path":"/tmp/test.js"}}\'');
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const [hookScript, stdinJSON] = args;
|
|
195
|
+
|
|
196
|
+
console.log(`\nRunning: ${hookScript}`);
|
|
197
|
+
console.log(`Stdin: ${stdinJSON.substring(0, 100)}...`);
|
|
198
|
+
console.log('');
|
|
199
|
+
|
|
200
|
+
const result = await runHook(hookScript, stdinJSON);
|
|
201
|
+
|
|
202
|
+
console.log(`Exit code: ${result.exitCode}`);
|
|
203
|
+
if (result.stdout.trim()) {
|
|
204
|
+
console.log(`Stdout: ${result.stdout.trim()}`);
|
|
205
|
+
}
|
|
206
|
+
if (result.stderr.trim()) {
|
|
207
|
+
console.log(`Stderr: ${result.stderr.trim()}`);
|
|
208
|
+
}
|
|
209
|
+
if (result.parsed) {
|
|
210
|
+
console.log(`Parsed JSON:`);
|
|
211
|
+
console.log(JSON.stringify(result.parsed, null, 2));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Run CLI mode if invoked directly
|
|
216
|
+
if (process.argv[1] && process.argv[1].includes('hook-test-harness')) {
|
|
217
|
+
main().catch(console.error);
|
|
218
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook Tests โ Validates hook behavior without Claude Code runtime.
|
|
5
|
+
*
|
|
6
|
+
* Run: node scripts/hooks/__tests__/hooks.test.js
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { runHook, assertHookResponse, assertSilent, runTests } from './hook-test-harness.js';
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// PreToolUse Tests
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
const preToolUseTests = [
|
|
16
|
+
{
|
|
17
|
+
name: 'Git commit without review triggers reminder or silent exit',
|
|
18
|
+
fn: async () => {
|
|
19
|
+
const result = await runHook('pre-tool-use.js', {
|
|
20
|
+
tool_name: 'Bash',
|
|
21
|
+
tool_input: { command: 'git commit -m "test commit"' },
|
|
22
|
+
});
|
|
23
|
+
// Should produce JSON output with review reminder
|
|
24
|
+
// OR exit silently if codeReviewDone=true from prior test
|
|
25
|
+
if (result.parsed) {
|
|
26
|
+
return assertHookResponse(result, 'PreToolUse');
|
|
27
|
+
}
|
|
28
|
+
// Silent exit is acceptable (review already done or no session file)
|
|
29
|
+
return assertSilent(result);
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'Non-git-commit Bash exits silently',
|
|
34
|
+
fn: async () => {
|
|
35
|
+
const result = await runHook('pre-tool-use.js', {
|
|
36
|
+
tool_name: 'Bash',
|
|
37
|
+
tool_input: { command: 'ls -la' },
|
|
38
|
+
});
|
|
39
|
+
return assertSilent(result);
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Non-Bash tool exits silently',
|
|
44
|
+
fn: async () => {
|
|
45
|
+
const result = await runHook('pre-tool-use.js', {
|
|
46
|
+
tool_name: 'Read',
|
|
47
|
+
tool_input: { file_path: '/tmp/test.js' },
|
|
48
|
+
});
|
|
49
|
+
return assertSilent(result);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'Git amend exits silently',
|
|
54
|
+
fn: async () => {
|
|
55
|
+
const result = await runHook('pre-tool-use.js', {
|
|
56
|
+
tool_name: 'Bash',
|
|
57
|
+
tool_input: { command: 'git commit --amend -m "fix"' },
|
|
58
|
+
});
|
|
59
|
+
return assertSilent(result);
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'Empty stdin exits without error',
|
|
64
|
+
fn: async () => {
|
|
65
|
+
const result = await runHook('pre-tool-use.js', '');
|
|
66
|
+
return result.exitCode === 0;
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// PreToolUse โ Smart Router Tests (1B)
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
const smartRouterTests = [
|
|
76
|
+
{
|
|
77
|
+
name: 'Task(Explore) gets model routing or silent exit',
|
|
78
|
+
fn: async () => {
|
|
79
|
+
const result = await runHook('pre-tool-use.js', {
|
|
80
|
+
tool_name: 'Task',
|
|
81
|
+
tool_input: { subagent_type: 'Explore', prompt: 'find auth code' },
|
|
82
|
+
});
|
|
83
|
+
// Either: hook produces routing output with haiku, or exits silently (no config)
|
|
84
|
+
if (!result.parsed) {
|
|
85
|
+
return assertSilent(result); // No output = no config, valid
|
|
86
|
+
}
|
|
87
|
+
const output = result.parsed?.hookSpecificOutput;
|
|
88
|
+
if (!output) return false; // Parsed but no hookSpecificOutput = malformed
|
|
89
|
+
// If routing was applied, model should be haiku
|
|
90
|
+
if (output.updatedInput?.model) {
|
|
91
|
+
return output.updatedInput.model === 'haiku';
|
|
92
|
+
}
|
|
93
|
+
// Output present but no model routing = other handler fired, OK
|
|
94
|
+
return true;
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'Task with explicit model preserves user choice',
|
|
99
|
+
fn: async () => {
|
|
100
|
+
const result = await runHook('pre-tool-use.js', {
|
|
101
|
+
tool_name: 'Task',
|
|
102
|
+
tool_input: { subagent_type: 'Explore', model: 'opus', prompt: 'deep analysis' },
|
|
103
|
+
});
|
|
104
|
+
// Should NOT override user's explicit model
|
|
105
|
+
if (result.parsed?.hookSpecificOutput?.updatedInput?.model) {
|
|
106
|
+
// Any model override when user specified 'opus' is wrong
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
// No model override = correct behavior
|
|
110
|
+
return true;
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'Task(Plan) gets planning template injected',
|
|
115
|
+
fn: async () => {
|
|
116
|
+
const result = await runHook('pre-tool-use.js', {
|
|
117
|
+
tool_name: 'Task',
|
|
118
|
+
tool_input: { subagent_type: 'Plan', prompt: 'plan the auth refactor' },
|
|
119
|
+
});
|
|
120
|
+
if (!result.parsed) {
|
|
121
|
+
// No output = template file not found. Acceptable but log it.
|
|
122
|
+
return assertSilent(result);
|
|
123
|
+
}
|
|
124
|
+
const output = result.parsed?.hookSpecificOutput;
|
|
125
|
+
if (!output) return false; // Parsed but no hookSpecificOutput = malformed
|
|
126
|
+
// Prompt should contain original + template content
|
|
127
|
+
if (output.updatedInput?.prompt) {
|
|
128
|
+
const prompt = output.updatedInput.prompt;
|
|
129
|
+
// Must contain original prompt AND some template content
|
|
130
|
+
return prompt.includes('plan the auth refactor') &&
|
|
131
|
+
(prompt.includes('Required Plan Sections') || prompt.includes('---'));
|
|
132
|
+
}
|
|
133
|
+
// No prompt modification = handler didn't fire, unexpected
|
|
134
|
+
return false;
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'EnterPlanMode gets context with template reference',
|
|
139
|
+
fn: async () => {
|
|
140
|
+
const result = await runHook('pre-tool-use.js', {
|
|
141
|
+
tool_name: 'EnterPlanMode',
|
|
142
|
+
tool_input: {},
|
|
143
|
+
});
|
|
144
|
+
if (!result.parsed) {
|
|
145
|
+
// No output = template file not found. Acceptable.
|
|
146
|
+
return assertSilent(result);
|
|
147
|
+
}
|
|
148
|
+
const output = result.parsed?.hookSpecificOutput;
|
|
149
|
+
if (!output) return false; // Parsed but no hookSpecificOutput = malformed
|
|
150
|
+
if (output.additionalContext) {
|
|
151
|
+
return output.additionalContext.includes('PLANNING MODE');
|
|
152
|
+
}
|
|
153
|
+
// hookSpecificOutput without additionalContext = wrong handler response
|
|
154
|
+
return false;
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'Non-Task tool is not affected by routing',
|
|
159
|
+
fn: async () => {
|
|
160
|
+
const result = await runHook('pre-tool-use.js', {
|
|
161
|
+
tool_name: 'Grep',
|
|
162
|
+
tool_input: { pattern: 'test', path: '/tmp' },
|
|
163
|
+
});
|
|
164
|
+
return assertSilent(result);
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
// ============================================================================
|
|
170
|
+
// PostToolUse Tests
|
|
171
|
+
// ============================================================================
|
|
172
|
+
|
|
173
|
+
const postToolUseTests = [
|
|
174
|
+
{
|
|
175
|
+
name: 'Read tool exits silently',
|
|
176
|
+
fn: async () => {
|
|
177
|
+
const result = await runHook('post-tool-use.js', {
|
|
178
|
+
tool_name: 'Read',
|
|
179
|
+
tool_input: { file_path: '/tmp/test.js' },
|
|
180
|
+
success: true,
|
|
181
|
+
});
|
|
182
|
+
return assertSilent(result);
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'Bash tool exits silently',
|
|
187
|
+
fn: async () => {
|
|
188
|
+
const result = await runHook('post-tool-use.js', {
|
|
189
|
+
tool_name: 'Bash',
|
|
190
|
+
tool_input: { command: 'echo hello' },
|
|
191
|
+
success: true,
|
|
192
|
+
});
|
|
193
|
+
return assertSilent(result);
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'Empty stdin exits without error',
|
|
198
|
+
fn: async () => {
|
|
199
|
+
const result = await runHook('post-tool-use.js', '');
|
|
200
|
+
return result.exitCode === 0;
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// PostCommit Tests
|
|
207
|
+
// ============================================================================
|
|
208
|
+
|
|
209
|
+
const postCommitTests = [
|
|
210
|
+
{
|
|
211
|
+
name: 'Non-git-commit exits silently',
|
|
212
|
+
fn: async () => {
|
|
213
|
+
const result = await runHook('post-commit.js', {
|
|
214
|
+
tool_name: 'Read',
|
|
215
|
+
tool_input: { file_path: '/tmp/test.js' },
|
|
216
|
+
success: true,
|
|
217
|
+
});
|
|
218
|
+
return assertSilent(result);
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: 'Failed command exits silently',
|
|
223
|
+
fn: async () => {
|
|
224
|
+
const result = await runHook('post-commit.js', {
|
|
225
|
+
tool_name: 'Bash',
|
|
226
|
+
tool_input: { command: 'git commit -m "test"' },
|
|
227
|
+
success: false,
|
|
228
|
+
});
|
|
229
|
+
return assertSilent(result);
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
// ============================================================================
|
|
235
|
+
// Run All Tests
|
|
236
|
+
// ============================================================================
|
|
237
|
+
|
|
238
|
+
async function main() {
|
|
239
|
+
console.log('\n๐งช Hook Test Suite\n');
|
|
240
|
+
|
|
241
|
+
let totalPassed = 0;
|
|
242
|
+
let totalFailed = 0;
|
|
243
|
+
|
|
244
|
+
const suites = [
|
|
245
|
+
{ name: 'PreToolUse Hook (Code Review)', tests: preToolUseTests },
|
|
246
|
+
{ name: 'PreToolUse Hook (Smart Router)', tests: smartRouterTests },
|
|
247
|
+
{ name: 'PostToolUse Hook', tests: postToolUseTests },
|
|
248
|
+
{ name: 'PostCommit Hook', tests: postCommitTests },
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
for (const suite of suites) {
|
|
252
|
+
const { passed, failed } = await runTests(suite.name, suite.tests);
|
|
253
|
+
totalPassed += passed;
|
|
254
|
+
totalFailed += failed;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
console.log('โ'.repeat(55));
|
|
258
|
+
console.log(` Total: ${totalPassed} passed, ${totalFailed} failed`);
|
|
259
|
+
console.log('โ'.repeat(55));
|
|
260
|
+
|
|
261
|
+
process.exit(totalFailed > 0 ? 1 : 0);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
main().catch(error => {
|
|
265
|
+
console.error('Test runner error:', error);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
});
|