termcode
Version:
Superior terminal AI coding agent with enterprise-grade security, intelligent error recovery, performance monitoring, and plugin system - Advanced Claude Code alternative
612 lines (611 loc) • 23.4 kB
JavaScript
import { sandbox } from "../security/sandbox.js";
/**
* Built-in hooks that provide enhanced Claude Code-inspired functionality
*/
export class BuiltinHooks {
/**
* Command validator hook - Claude Code inspired
*/
static commandValidator = {
id: 'builtin_command_validator',
name: 'Command Validator',
description: 'Validates and suggests improvements for shell commands',
type: 'PreToolUse',
matcher: {
toolNames: ['shell', 'bash']
},
handler: {
type: 'builtin',
builtin: 'command_validator'
},
priority: 10,
enabled: true,
timeout: 5000,
retries: 0
};
/**
* Security scanner hook
*/
static securityScanner = {
id: 'builtin_security_scanner',
name: 'Security Scanner',
description: 'Scans for potential security issues in commands and code',
type: 'PreToolUse',
matcher: {},
handler: {
type: 'builtin',
builtin: 'security_scanner'
},
priority: 5,
enabled: true,
timeout: 10000,
retries: 1
};
/**
* Diff optimizer hook
*/
static diffOptimizer = {
id: 'builtin_diff_optimizer',
name: 'Diff Optimizer',
description: 'Optimizes and validates diffs before application',
type: 'PreDiff',
matcher: {},
handler: {
type: 'builtin',
builtin: 'diff_optimizer'
},
priority: 20,
enabled: true,
timeout: 15000,
retries: 0
};
/**
* Commit enhancer hook
*/
static commitEnhancer = {
id: 'builtin_commit_enhancer',
name: 'Commit Enhancer',
description: 'Enhances commit messages and validates changes',
type: 'PreCommit',
matcher: {},
handler: {
type: 'builtin',
builtin: 'commit_enhancer'
},
priority: 15,
enabled: true,
timeout: 10000,
retries: 0
};
/**
* Error analyzer hook
*/
static errorAnalyzer = {
id: 'builtin_error_analyzer',
name: 'Error Analyzer',
description: 'Analyzes errors and provides intelligent suggestions',
type: 'OnError',
matcher: {},
handler: {
type: 'builtin',
builtin: 'error_analyzer'
},
priority: 50,
enabled: true,
timeout: 8000,
retries: 0
};
/**
* Performance monitor hook
*/
static performanceMonitor = {
id: 'builtin_performance_monitor',
name: 'Performance Monitor',
description: 'Monitors and reports on operation performance',
type: 'PostTask',
matcher: {},
handler: {
type: 'builtin',
builtin: 'performance_monitor'
},
priority: 100,
enabled: true,
timeout: 5000,
retries: 0
};
/**
* Get all built-in hooks
*/
static getAll() {
return [
this.commandValidator,
this.securityScanner,
this.diffOptimizer,
this.commitEnhancer,
this.errorAnalyzer,
this.performanceMonitor
];
}
/**
* Execute built-in hook logic
*/
static async execute(hookType, context, additionalContext) {
switch (hookType) {
case 'command_validator':
return this.executeCommandValidator(context);
case 'security_scanner':
return this.executeSecurityScanner(context, additionalContext);
case 'diff_optimizer':
return this.executeDiffOptimizer(context);
case 'commit_enhancer':
return this.executeCommitEnhancer(context);
case 'error_analyzer':
return this.executeErrorAnalyzer(context, additionalContext);
case 'performance_monitor':
return this.executePerformanceMonitor(context, additionalContext);
default:
return { success: false, error: `Unknown builtin hook: ${hookType}` };
}
}
/**
* Command validator implementation
*/
static async executeCommandValidator(context) {
const { toolInput } = context;
if (!toolInput?.command) {
return { success: true };
}
const command = Array.isArray(toolInput.command) ? toolInput.command : [toolInput.command];
const suggestions = [];
let transformedInput = toolInput;
// Validate command safety
const securityCheck = await sandbox.execute(command, context.repoPath);
if (securityCheck.blocked) {
return {
success: false,
error: `CRITICAL: Command blocked by security policy: ${securityCheck.blocked}`,
suggestions: ['Review command for security issues', 'Use allowed alternatives']
};
}
// Command optimization suggestions (Claude Code style)
const optimizations = this.getCommandOptimizations(command);
if (optimizations.suggestions.length > 0) {
suggestions.push(...optimizations.suggestions);
if (optimizations.transformedCommand) {
transformedInput = {
...toolInput,
command: optimizations.transformedCommand
};
}
}
// Performance warnings
const performanceWarnings = this.getPerformanceWarnings(command);
suggestions.push(...performanceWarnings);
return {
success: true,
suggestions,
transformedInput: suggestions.length > 0 ? transformedInput : undefined,
metadata: {
originalCommand: command,
optimized: optimizations.transformedCommand !== undefined
}
};
}
/**
* Security scanner implementation
*/
static async executeSecurityScanner(context, additionalContext) {
const issues = [];
const suggestions = [];
// Check for credential exposure
const credentialCheck = this.checkForCredentials(additionalContext);
if (credentialCheck.found) {
issues.push('Potential credentials detected in input');
suggestions.push('Remove or redact sensitive information');
}
// Check for dangerous patterns
const dangerousPatterns = this.checkDangerousPatterns(additionalContext);
issues.push(...dangerousPatterns);
// Environment security check
const envCheck = this.checkEnvironmentSecurity(context);
issues.push(...envCheck);
if (issues.length > 0) {
return {
success: false,
error: `Security issues detected: ${issues.join(', ')}`,
suggestions,
metadata: { securityIssues: issues }
};
}
return { success: true, metadata: { securityScan: 'passed' } };
}
/**
* Diff optimizer implementation
*/
static async executeDiffOptimizer(context) {
const suggestions = [];
const optimizations = [];
for (const diff of context.diffs) {
// Check for large changes
const lines = diff.unified.split('\n');
const additions = lines.filter(l => l.startsWith('+')).length;
const deletions = lines.filter(l => l.startsWith('-')).length;
if (additions + deletions > 100) {
suggestions.push(`Large diff in ${diff.file} (${additions}+, ${deletions}-). Consider breaking into smaller changes.`);
}
// Check for sensitive file changes
if (this.isSensitiveFile(diff.file)) {
suggestions.push(`Changes to sensitive file ${diff.file} - review carefully`);
}
// Detect potential issues
const issues = this.analyzeDiffContent(diff);
suggestions.push(...issues);
// Suggest improvements
const improvements = this.suggestDiffImprovements(diff);
optimizations.push(...improvements);
}
return {
success: true,
suggestions,
metadata: {
totalFiles: context.diffs.length,
optimizations,
largeChanges: suggestions.filter(s => s.includes('Large diff')).length
}
};
}
/**
* Commit enhancer implementation
*/
static async executeCommitEnhancer(context) {
const suggestions = [];
let enhancedMessage = context.message;
// Check commit message quality
if (context.message.length < 10) {
suggestions.push('Commit message is too short - provide more detail');
}
if (!context.message.match(/^(feat|fix|docs|style|refactor|test|chore):/)) {
const type = this.inferCommitType(context);
enhancedMessage = `${type}: ${context.message}`;
suggestions.push(`Consider using conventional commit format: ${enhancedMessage}`);
}
// Add file statistics if significant
if (context.stats.files > 10) {
enhancedMessage += `\n\nChanged ${context.stats.files} files (+${context.stats.additions} -${context.stats.deletions})`;
}
// Check for breaking changes
const breakingChanges = this.detectBreakingChanges(context);
if (breakingChanges.length > 0) {
enhancedMessage += '\n\nBREAKING CHANGES:\n' + breakingChanges.join('\n');
suggestions.push('Breaking changes detected - ensure proper versioning');
}
return {
success: true,
suggestions,
transformedInput: enhancedMessage !== context.message ? { message: enhancedMessage } : undefined,
metadata: {
originalLength: context.message.length,
enhancedLength: enhancedMessage.length,
hasBreakingChanges: breakingChanges.length > 0
}
};
}
/**
* Error analyzer implementation
*/
static async executeErrorAnalyzer(context, additionalContext) {
const error = additionalContext?.error || '';
const suggestions = [];
const solutions = [];
// Common error patterns and solutions
const errorPatterns = [
{
pattern: /ENOENT.*package\.json/,
suggestion: 'Run npm init to create package.json',
solution: 'npm init -y'
},
{
pattern: /Module not found/,
suggestion: 'Install missing dependency',
solution: 'npm install <missing-module>'
},
{
pattern: /Permission denied/,
suggestion: 'Check file permissions or use sudo if appropriate',
solution: 'chmod +x <file> or sudo <command>'
},
{
pattern: /git.*not a git repository/,
suggestion: 'Initialize git repository',
solution: 'git init'
},
{
pattern: /No such file or directory/,
suggestion: 'Verify file path exists',
solution: 'Check file path and create if necessary'
}
];
// Match error patterns
for (const { pattern, suggestion, solution } of errorPatterns) {
if (pattern.test(error)) {
suggestions.push(suggestion);
solutions.push(solution);
}
}
// Provider-specific error handling
const providerSuggestions = this.getProviderSpecificSuggestions(context.provider, error);
suggestions.push(...providerSuggestions);
// Generic suggestions if no specific match
if (suggestions.length === 0) {
suggestions.push('Check command syntax and arguments', 'Verify required dependencies are installed', 'Review error message for specific details');
}
return {
success: true,
suggestions,
data: solutions,
metadata: {
errorType: this.categorizeError(error),
provider: context.provider,
hasAutofix: solutions.length > 0
}
};
}
/**
* Performance monitor implementation
*/
static async executePerformanceMonitor(context, additionalContext) {
const performance = additionalContext?.performance || {};
const warnings = [];
const metrics = {};
// Check execution time
if (performance.executionTime > 30000) {
warnings.push(`Long execution time: ${performance.executionTime}ms`);
}
// Memory usage check
if (performance.memoryUsage && performance.memoryUsage > 500 * 1024 * 1024) {
warnings.push(`High memory usage: ${Math.round(performance.memoryUsage / 1024 / 1024)}MB`);
}
// Token usage analysis
if (performance.tokenCount > 8000) {
warnings.push(`High token usage: ${performance.tokenCount} tokens`);
}
// Rate limiting check
if (performance.rateLimited) {
warnings.push('Rate limiting detected - consider throttling requests');
}
metrics.executionTime = performance.executionTime;
metrics.memoryUsage = performance.memoryUsage;
metrics.tokenCount = performance.tokenCount;
metrics.provider = context.provider;
metrics.model = context.model;
return {
success: true,
suggestions: warnings,
data: metrics,
metadata: {
performanceGrade: this.calculatePerformanceGrade(performance),
optimizationSuggestions: this.getOptimizationSuggestions(performance)
}
};
}
// Helper methods for built-in hooks
static getCommandOptimizations(command) {
const suggestions = [];
let transformedCommand;
const cmdStr = command.join(' ');
// grep -> rg optimization (Claude Code style)
if (cmdStr.includes('grep ') && !cmdStr.includes('|')) {
suggestions.push("Use 'rg' (ripgrep) instead of 'grep' for better performance");
transformedCommand = command.map(arg => arg === 'grep' ? 'rg' : arg);
}
// find -> rg optimization
if (cmdStr.match(/find\s+\S+\s+-name/)) {
suggestions.push("Use 'rg --files -g pattern' instead of 'find -name' for better performance");
}
// npm -> pnpm suggestion
if (command[0] === 'npm' && command[1] === 'install') {
suggestions.push("Consider using 'pnpm' for faster installs and better disk usage");
}
// Add parallel flag suggestions
if (command[0] === 'npm' && command.includes('test')) {
suggestions.push("Add --parallel flag for faster test execution");
}
return { suggestions, transformedCommand };
}
static getPerformanceWarnings(command) {
const warnings = [];
const cmdStr = command.join(' ');
// Warn about potentially slow operations
if (cmdStr.includes('npm install') && !cmdStr.includes('--production')) {
warnings.push('Installing dev dependencies - use --production for faster installs in production');
}
if (cmdStr.includes('recursive') || cmdStr.includes('-r')) {
warnings.push('Recursive operation detected - may be slow on large directories');
}
return warnings;
}
static checkForCredentials(data) {
const text = JSON.stringify(data);
const patterns = [
{ pattern: /password\s*[:=]\s*['"][^'"]+['"]/, type: 'password' },
{ pattern: /api[_-]?key\s*[:=]\s*['"][^'"]+['"]/, type: 'api_key' },
{ pattern: /secret\s*[:=]\s*['"][^'"]+['"]/, type: 'secret' },
{ pattern: /token\s*[:=]\s*['"][^'"]+['"]/, type: 'token' },
];
const found = patterns.some(p => p.pattern.test(text));
const types = patterns.filter(p => p.pattern.test(text)).map(p => p.type);
return { found, types };
}
static checkDangerousPatterns(data) {
const issues = [];
const text = JSON.stringify(data);
const dangerousPatterns = [
{ pattern: /rm\s+-rf\s+\//, issue: 'Dangerous recursive delete of root directory' },
{ pattern: /sudo\s+.*\|\s*sh/, issue: 'Piping to shell with sudo privileges' },
{ pattern: /curl.*\|\s*sh/, issue: 'Executing downloaded script directly' },
{ pattern: /eval\s*\(/, issue: 'Dynamic code evaluation detected' },
];
for (const { pattern, issue } of dangerousPatterns) {
if (pattern.test(text)) {
issues.push(issue);
}
}
return issues;
}
static checkEnvironmentSecurity(context) {
const issues = [];
// Check for development environment in production context
if (context.environment.NODE_ENV === 'production' && context.environment.DEBUG) {
issues.push('Debug mode enabled in production environment');
}
// Check for missing security variables
if (!context.environment.NODE_ENV) {
issues.push('NODE_ENV not set - may cause security issues');
}
return issues;
}
static isSensitiveFile(filePath) {
const sensitivePatterns = [
/\.env/,
/\.key$/,
/\.pem$/,
/\.p12$/,
/config\/database/,
/config\/secrets/,
/\.ssh\//,
/package-lock\.json$/,
/yarn\.lock$/
];
return sensitivePatterns.some(pattern => pattern.test(filePath));
}
static analyzeDiffContent(diff) {
const issues = [];
const lines = diff.unified.split('\n');
// Check for debug code
const debugPatterns = [/console\.log/, /debugger/, /print\(/, /dump\(/];
if (lines.some(line => debugPatterns.some(pattern => pattern.test(line)))) {
issues.push(`Possible debug code in ${diff.file}`);
}
// Check for TODO/FIXME comments
const todoPatterns = [/TODO/, /FIXME/, /HACK/, /XXX/];
if (lines.some(line => todoPatterns.some(pattern => pattern.test(line)))) {
issues.push(`TODO/FIXME comments in ${diff.file}`);
}
return issues;
}
static suggestDiffImprovements(diff) {
const improvements = [];
// Suggest formatting if mixed indentation
const lines = diff.newContent.split('\n');
const hasSpaces = lines.some(line => line.startsWith(' '));
const hasTabs = lines.some(line => line.startsWith('\t'));
if (hasSpaces && hasTabs) {
improvements.push({
type: 'formatting',
message: `Mixed indentation in ${diff.file}`,
suggestion: 'Use consistent indentation (spaces or tabs)'
});
}
return improvements;
}
static inferCommitType(context) {
const files = context.files;
// Infer type based on files changed
if (files.some(f => f.includes('test')))
return 'test';
if (files.some(f => f.endsWith('.md')))
return 'docs';
if (files.some(f => f.includes('config') || f.includes('.json')))
return 'chore';
if (files.some(f => f.includes('fix') || context.message.toLowerCase().includes('fix')))
return 'fix';
return 'feat';
}
static detectBreakingChanges(context) {
const breaking = [];
// Simple heuristics for breaking changes
if (context.message.toLowerCase().includes('breaking')) {
breaking.push('Explicit breaking change mentioned in commit message');
}
if (context.stats.deletions > context.stats.additions * 2) {
breaking.push('Large amount of code removed - potential breaking change');
}
return breaking;
}
static getProviderSpecificSuggestions(provider, error) {
const suggestions = [];
switch (provider) {
case 'openai':
if (error.includes('rate_limit_exceeded')) {
suggestions.push('OpenAI rate limit exceeded - wait and retry or upgrade plan');
}
if (error.includes('context_length_exceeded')) {
suggestions.push('Context too long for OpenAI - reduce input size or use different model');
}
break;
case 'anthropic':
if (error.includes('rate_limit')) {
suggestions.push('Anthropic rate limit - consider using Claude-3-haiku for lighter tasks');
}
break;
case 'ollama':
if (error.includes('connection refused')) {
suggestions.push('Ollama server not running - start with: ollama serve');
}
break;
}
return suggestions;
}
static categorizeError(error) {
if (error.includes('permission') || error.includes('access'))
return 'permission';
if (error.includes('not found') || error.includes('ENOENT'))
return 'file_not_found';
if (error.includes('network') || error.includes('connection'))
return 'network';
if (error.includes('syntax') || error.includes('parse'))
return 'syntax';
if (error.includes('rate limit'))
return 'rate_limit';
if (error.includes('timeout'))
return 'timeout';
return 'unknown';
}
static calculatePerformanceGrade(performance) {
let score = 100;
if (performance.executionTime > 60000)
score -= 30;
else if (performance.executionTime > 30000)
score -= 15;
if (performance.memoryUsage > 1024 * 1024 * 1024)
score -= 20;
else if (performance.memoryUsage > 500 * 1024 * 1024)
score -= 10;
if (performance.tokenCount > 10000)
score -= 15;
else if (performance.tokenCount > 5000)
score -= 5;
if (score >= 90)
return 'A';
if (score >= 80)
return 'B';
if (score >= 70)
return 'C';
if (score >= 60)
return 'D';
return 'F';
}
static getOptimizationSuggestions(performance) {
const suggestions = [];
if (performance.executionTime > 30000) {
suggestions.push('Consider breaking large tasks into smaller chunks');
}
if (performance.tokenCount > 8000) {
suggestions.push('Reduce context size or use summarization');
}
if (performance.memoryUsage > 500 * 1024 * 1024) {
suggestions.push('Optimize memory usage - avoid loading large files entirely');
}
return suggestions;
}
}