Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:14:56 +08:00
commit fe5ac25e03
9 changed files with 2612 additions and 0 deletions

17
hooks/hooks.json Normal file
View File

@@ -0,0 +1,17 @@
{
"description": "Suggests test files when TypeScript source files are read",
"hooks": {
"PostToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/test-file-suggest.ts",
"timeout": 30
}
]
}
]
}
}

88
hooks/test-file-suggest.ts Executable file
View File

@@ -0,0 +1,88 @@
#!/usr/bin/env bun
import { existsSync, readFileSync } from 'node:fs';
import { basename, dirname, join } from 'node:path';
import type { PostToolUseHookInput, SyncHookJSONOutput } from '@anthropic-ai/claude-agent-sdk';
async function main() {
try {
// Read input from stdin
const input = readFileSync(0, 'utf-8');
const data: PostToolUseHookInput = JSON.parse(input);
// Check if the tool was Read
if (data.tool_name !== 'Read') {
process.exit(0);
}
// Get the file path from tool input
const toolInput = data.tool_input as Record<string, unknown>;
const filePath = toolInput.file_path as string;
if (!filePath) {
process.exit(0);
}
// Check if it's a TypeScript file (but not already a test file)
const isTypeScriptFile =
(filePath.endsWith('.ts') || filePath.endsWith('.tsx')) &&
!filePath.endsWith('.test.ts') &&
!filePath.endsWith('.test.tsx') &&
!filePath.endsWith('.spec.ts') &&
!filePath.endsWith('.spec.tsx');
if (!isTypeScriptFile) {
process.exit(0);
}
// Look for corresponding test files
const dir = dirname(filePath);
const file = basename(filePath);
const nameWithoutExt = file.replace(/\.tsx?$/, '');
const testPatterns = [
`${nameWithoutExt}.test.ts`,
`${nameWithoutExt}.test.tsx`,
`${nameWithoutExt}.spec.ts`,
`${nameWithoutExt}.spec.tsx`,
];
const foundTestFiles: string[] = [];
for (const pattern of testPatterns) {
const testPath = join(dir, pattern);
if (existsSync(testPath)) {
foundTestFiles.push(testPath);
}
}
// If test files were found, suggest them
if (foundTestFiles.length > 0) {
let context = '<plugin-langs-suggestion>\n';
context += `Source file: ${filePath}\n\n`;
context += 'Related test file(s):\n';
for (const testFile of foundTestFiles) {
context += `${testFile}\n`;
}
context += '</plugin-langs-suggestion>';
// Return JSON with hookSpecificOutput for PostToolUse
const output: SyncHookJSONOutput = {
hookSpecificOutput: {
hookEventName: 'PostToolUse',
additionalContext: context,
},
};
console.log(JSON.stringify(output));
}
// Exit 0 = success
process.exit(0);
} catch (err) {
console.error('Error in test-file-suggest hook:', err);
process.exit(1);
}
}
main().catch((err) => {
console.error('Uncaught error:', err);
process.exit(1);
});