rulecrafter
Version:
Adaptive automation system that learns from your Claude Code workflow and generates intelligent rules and commands
427 lines (355 loc) • 14 kB
JavaScript
#!/usr/bin/env node
const { program } = require('commander');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const packageJson = require('../package.json');
program
.name('rulecrafter')
.description('RuleCrafter - Adaptive automation system that learns from your development patterns')
.version(packageJson.version);
program
.command('init')
.description('Initialize RuleCrafter in the current project')
.option('-f, --force', 'Overwrite existing installation')
.action(async (options) => {
console.log('🚀 Initializing RuleCrafter...');
const currentDir = process.cwd();
const claudeDir = path.join(currentDir, '.claude');
const rulecrafterDir = path.join(claudeDir, 'rulecrafter');
// Check if already initialized
if (fs.existsSync(rulecrafterDir) && !options.force) {
console.log('❌ RuleCrafter is already initialized in this project.');
console.log(' Use --force to overwrite the existing installation.');
return;
}
try {
// Create .claude directory if it doesn't exist
if (!fs.existsSync(claudeDir)) {
fs.mkdirSync(claudeDir, { recursive: true });
}
// Copy template structure
const templateDir = path.join(__dirname, '..', 'templates', '.claude');
copyDirectory(templateDir, claudeDir);
// Make hook scripts executable
const hooksDir = path.join(rulecrafterDir, 'hooks');
if (fs.existsSync(hooksDir)) {
const hookFiles = fs.readdirSync(hooksDir);
hookFiles.forEach(file => {
if (file.endsWith('.sh')) {
const filePath = path.join(hooksDir, file);
fs.chmodSync(filePath, '755');
}
});
}
// Initialize CLAUDE.md if it doesn't exist
const claudeMdPath = path.join(currentDir, 'CLAUDE.md');
if (!fs.existsSync(claudeMdPath)) {
createInitialClaudeMd(claudeMdPath);
}
// Auto-generate Claude Code hooks configuration
const settingsPath = path.join(claudeDir, 'settings.local.json');
const claudeSettings = {
hooks: {
PreToolUse: [
{
matcher: "*",
hooks: [
{
type: "command",
command: path.join(currentDir, '.claude', 'rulecrafter', 'hooks', 'pre_tool_analyzer.sh')
}
]
}
],
PostToolUse: [
{
matcher: "*",
hooks: [
{
type: "command",
command: path.join(currentDir, '.claude', 'rulecrafter', 'hooks', 'post_tool_learner.sh')
}
]
}
],
Stop: [
{
matcher: "*",
hooks: [
{
type: "command",
command: path.join(currentDir, '.claude', 'rulecrafter', 'hooks', 'session_compact.sh')
}
]
}
]
}
};
fs.writeFileSync(settingsPath, JSON.stringify(claudeSettings, null, 2));
console.log('✅ RuleCrafter initialized successfully!');
console.log('');
console.log('📁 Created structure:');
console.log(' .claude/rulecrafter/ - Core system files');
console.log(' .claude/commands/ - Auto-generated slash commands');
console.log(' .claude/settings.local.json - Auto-configured hooks');
console.log(' CLAUDE.md - Project memory (if not exists)');
console.log('');
console.log('🎯 Setup complete! Claude Code hooks are automatically configured.');
console.log('');
console.log('🚀 To start using RuleCrafter:');
console.log(' 1. Start Claude Code in this project: claude');
console.log(' 2. Begin coding normally - RuleCrafter learns automatically!');
console.log('');
console.log('🔍 Verify hooks: rulecrafter verify-hooks');
} catch (error) {
console.error('❌ Failed to initialize RuleCrafter:', error.message);
process.exit(1);
}
});
program
.command('status')
.description('Show RuleCrafter status for the current project')
.action(() => {
const currentDir = process.cwd();
const rulecrafterDir = path.join(currentDir, '.claude', 'rulecrafter');
if (!fs.existsSync(rulecrafterDir)) {
console.log('❌ RuleCrafter is not initialized in this project.');
console.log(' Run: rulecrafter init');
return;
}
console.log('📊 RuleCrafter Status');
console.log('===================');
// Check for patterns file
const patternsPath = path.join(rulecrafterDir, 'storage', 'patterns.json');
if (fs.existsSync(patternsPath)) {
try {
const patterns = JSON.parse(fs.readFileSync(patternsPath, 'utf8'));
console.log(`📈 Patterns collected: ${Object.keys(patterns.commands || {}).length} commands`);
console.log(`🐛 Errors tracked: ${Object.keys(patterns.errors || {}).length} error types`);
} catch (error) {
console.log('⚠️ Patterns file exists but is invalid');
}
} else {
console.log('📈 No patterns collected yet');
}
// Check for auto-generated commands
const autoGenDir = path.join(currentDir, '.claude', 'commands', 'auto-generated');
if (fs.existsSync(autoGenDir)) {
const commands = fs.readdirSync(autoGenDir).filter(f => f.endsWith('.md'));
console.log(`🤖 Auto-generated commands: ${commands.length}`);
if (commands.length > 0) {
commands.forEach(cmd => {
console.log(` - /${cmd.replace('.md', '')}`);
});
}
} else {
console.log('🤖 No auto-generated commands yet');
}
console.log('');
// Quick hook validation
const hooksDir = path.join(rulecrafterDir, 'hooks');
const hookFiles = ['pre_tool_analyzer.sh', 'post_tool_learner.sh', 'session_compact.sh'];
const validHooks = hookFiles.filter(file => {
const hookPath = path.join(hooksDir, file);
try {
fs.accessSync(hookPath, fs.constants.X_OK);
return true;
} catch {
return false;
}
});
if (validHooks.length === hookFiles.length) {
console.log('🪝 Hooks: Ready (run "rulecrafter verify-hooks" for details)');
} else {
console.log('⚠️ Hooks: Need configuration (run "rulecrafter verify-hooks")');
}
console.log('✅ System is ready and learning from your workflow');
});
program
.command('clean')
.description('Remove RuleCrafter from the current project')
.option('-y, --yes', 'Skip confirmation prompt')
.action(async (options) => {
const currentDir = process.cwd();
const rulecrafterDir = path.join(currentDir, '.claude', 'rulecrafter');
if (!fs.existsSync(rulecrafterDir)) {
console.log('❌ RuleCrafter is not installed in this project.');
return;
}
if (!options.yes) {
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
const answer = await new Promise(resolve => {
readline.question('🗑️ Remove RuleCrafter? This will delete all learned patterns. (y/N): ', resolve);
});
readline.close();
if (answer.toLowerCase() !== 'y') {
console.log('Cancelled.');
return;
}
}
try {
// Remove rulecrafter directory
fs.rmSync(rulecrafterDir, { recursive: true, force: true });
// Remove auto-generated commands
const autoGenDir = path.join(currentDir, '.claude', 'commands', 'auto-generated');
if (fs.existsSync(autoGenDir)) {
fs.rmSync(autoGenDir, { recursive: true, force: true });
}
console.log('✅ RuleCrafter removed successfully');
console.log('');
console.log('� Manual cleanup required in Claude Code:');
console.log(' 1. Type "/hooks" in Claude Code');
console.log(' 2. Remove these RuleCrafter hooks:');
console.log(' - RuleCrafter Pre-Tool Analyzer');
console.log(' - RuleCrafter Post-Tool Learner');
console.log(' - RuleCrafter Session Compact');
console.log('');
console.log('💡 Or edit .claude/settings.json manually to remove hook entries');
} catch (error) {
console.error('❌ Failed to remove RuleCrafter:', error.message);
process.exit(1);
}
});
program
.command('verify-hooks')
.description('Verify that RuleCrafter hooks are properly configured in Claude Code')
.action(() => {
const currentDir = process.cwd();
const rulecrafterDir = path.join(currentDir, '.claude', 'rulecrafter');
if (!fs.existsSync(rulecrafterDir)) {
console.log('❌ RuleCrafter is not initialized in this project.');
console.log(' Run: rulecrafter init');
return;
}
console.log('🔍 Verifying RuleCrafter Hook Configuration');
console.log('==========================================');
// Check if hook scripts exist and are executable
const hooks = [
'pre_tool_analyzer.sh',
'post_tool_learner.sh',
'session_compact.sh'
];
const hooksDir = path.join(rulecrafterDir, 'hooks');
let allHooksValid = true;
hooks.forEach(hookFile => {
const hookPath = path.join(hooksDir, hookFile);
if (fs.existsSync(hookPath)) {
try {
fs.accessSync(hookPath, fs.constants.X_OK);
console.log(`✅ ${hookFile} - exists and executable`);
} catch (error) {
console.log(`⚠️ ${hookFile} - exists but not executable`);
console.log(` Fix with: chmod +x "${hookPath}"`);
allHooksValid = false;
}
} else {
console.log(`❌ ${hookFile} - missing`);
allHooksValid = false;
}
});
console.log('');
// Check Claude Code settings file for hooks
const settingsPath = path.join(currentDir, '.claude', 'settings.local.json');
let foundSettings = false;
let hooksConfigured = false;
if (fs.existsSync(settingsPath)) {
foundSettings = true;
console.log(`📁 Found Claude Code settings: settings.local.json`);
try {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
if (settings.hooks) {
let rulecrafterHookCount = 0;
// Check each hook type (PreToolUse, PostToolUse, Stop)
for (const hookType of ['PreToolUse', 'PostToolUse', 'Stop']) {
if (settings.hooks[hookType]) {
for (const matcher of settings.hooks[hookType]) {
if (matcher.hooks) {
for (const hook of matcher.hooks) {
if (hook.command && hook.command.includes('rulecrafter')) {
rulecrafterHookCount++;
}
}
}
}
}
}
if (rulecrafterHookCount > 0) {
console.log(`✅ Found ${rulecrafterHookCount} RuleCrafter hooks in settings`);
hooksConfigured = true;
}
}
} catch (error) {
console.log(`⚠️ Could not parse settings file: ${error.message}`);
}
}
if (!foundSettings) {
console.log('📁 No Claude Code settings file found');
console.log(' Run "rulecrafter init" to set up automatic hook configuration');
}
console.log('');
console.log('📋 Hook Configuration Summary:');
if (allHooksValid) {
console.log('✅ All hook scripts are ready');
} else {
console.log('❌ Some hook scripts need attention');
}
if (hooksConfigured) {
console.log('✅ Hooks are configured in Claude Code');
} else {
console.log('⚠️ Hooks may not be configured in Claude Code yet');
console.log(' Run "rulecrafter init" to set up automatic hook configuration');
}
console.log('');
if (allHooksValid && hooksConfigured) {
console.log('🎉 RuleCrafter hooks are properly configured!');
} else {
console.log('🔧 Next steps:');
if (!allHooksValid) {
console.log(' 1. Fix hook script permissions');
}
if (!hooksConfigured) {
console.log(' 2. Run "rulecrafter init" to set up automatic hook configuration');
}
}
});
// Helper functions
function copyDirectory(src, dest) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const items = fs.readdirSync(src);
for (const item of items) {
const srcPath = path.join(src, item);
const destPath = path.join(dest, item);
const stat = fs.statSync(srcPath);
if (stat.isDirectory()) {
copyDirectory(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
function createInitialClaudeMd(filePath) {
const content = `# Project Memory
This file contains project-specific information, coding standards, and preferences for Claude Code.
## Project Overview
<!-- Add your project description here -->
## Coding Standards
<!-- Add your coding standards and conventions here -->
## Common Patterns
<!-- Add frequently used patterns and workflows here -->
## RuleCrafter Adaptive Rules
<!-- This section will be automatically updated by RuleCrafter -->
<!-- DO NOT EDIT THIS SECTION MANUALLY -->
*No adaptive rules generated yet. RuleCrafter will populate this section as it learns from your workflow.*
---
*Generated by RuleCrafter - Learn more at: https://github.com/your-username/rulecrafter*
`;
fs.writeFileSync(filePath, content);
}
// Parse command line arguments
program.parse();