feat: initial commit for omp-hooks plugin

This commit is contained in:
Agent committed 2026-10-13 07:54:44 +00:00
commit a0d4230537
6 files changed
+624

No files matched your search

+108
View File
@@ -0,0 +1,108 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import os from 'node:os'
import {
parseClaudeConfig,
matchesPattern,
substituteCommand,
parseHookStdout,
executeHookCommand,
findProjectHookConfig,
} from '../index.js'
test('substituteCommand replaces CLAUDE_PROJECT_DIR and CLAUDE_PLUGIN_ROOT', () => {
const cmd = 'python3 ${CLAUDE_PROJECT_DIR}/check.py --root ${CLAUDE_PLUGIN_ROOT}'
const res = substituteCommand(cmd, {
projectDir: '/my/proj',
pluginRoot: '/my/plugin',
})
assert.equal(res, 'python3 /my/proj/check.py --root /my/plugin')
})
test('parseClaudeConfig handles settings.json format with matchers and timeouts', () => {
const raw = {
hooks: {
PreToolUse: [
{
matcher: 'bash|write',
hooks: [
{ type: 'command', command: 'echo "check"', timeout: 5 },
],
},
],
UserPromptSubmit: [
{
hooks: [
{ type: 'command', command: 'echo "prompt"' },
],
},
],
},
}
const parsed = parseClaudeConfig(raw, { projectDir: '/app' })
assert.ok(parsed.PreToolUse)
assert.equal(parsed.PreToolUse[0].matcher, 'bash|write')
assert.equal(parsed.PreToolUse[0].hooks[0].timeoutSec, 5)
assert.ok(parsed.UserPromptSubmit)
})
test('matchesPattern tests exact, regex, wildcard, and empty', () => {
assert.equal(matchesPattern('*', 'bash'), true)
assert.equal(matchesPattern('', 'bash'), true)
assert.equal(matchesPattern(undefined, 'bash'), true)
assert.equal(matchesPattern('bash', 'bash'), true)
assert.equal(matchesPattern('bash', 'read'), false)
assert.equal(matchesPattern('Edit|Write', 'Edit'), true)
assert.equal(matchesPattern('Edit|Write', 'Write'), true)
assert.equal(matchesPattern('Edit|Write', 'bash'), false)
})
test('parseHookStdout handles exit code 2 as deny', () => {
const res = parseHookStdout('', 2, 'Sensitive file blocked')
assert.equal(res.decision, 'deny')
assert.equal(res.reason, 'Sensitive file blocked')
})
test('parseHookStdout handles JSON decision and context', () => {
const jsonOut = JSON.stringify({
decision: 'deny',
reason: 'Security violation',
additionalContext: 'Remember security rule 101',
})
const res = parseHookStdout(jsonOut, 0, '')
assert.equal(res.decision, 'deny')
assert.equal(res.reason, 'Security violation')
assert.deepEqual(res.additionalContext, ['Remember security rule 101'])
})
test('executeHookCommand runs shell command and passes stdin payload', async () => {
const res = await executeHookCommand(
'read line; echo "Received: $line"',
{ tool: 'test_tool' },
{ timeoutSec: 5 }
)
assert.equal(res.exitCode, 0)
assert.ok(res.stdout.includes('Received: {"tool":"test_tool"}'))
})
test('findProjectHookConfig discovers .claude/settings.json', () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'hooks-test-'))
const claudeDir = path.join(tmp, '.claude')
fs.mkdirSync(claudeDir)
fs.writeFileSync(path.join(claudeDir, 'settings.json'), JSON.stringify({
hooks: {
PreToolUse: [
{ matcher: 'bash', hooks: [{ type: 'command', command: 'echo 1' }] },
],
},
}))
const conf = findProjectHookConfig(tmp)
assert.ok(conf)
assert.ok(conf.PreToolUse)
fs.rmSync(tmp, { recursive: true, force: true })
})