ruvector 0.1.44 → 0.1.46
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/bin/cli.js +523 -14
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -2267,7 +2267,51 @@ class Intelligence {
|
|
|
2267
2267
|
// Hooks command group
|
|
2268
2268
|
const hooksCmd = program.command('hooks').description('Self-learning intelligence hooks for Claude Code');
|
|
2269
2269
|
|
|
2270
|
-
|
|
2270
|
+
// Helper: Detect project type
|
|
2271
|
+
function detectProjectType() {
|
|
2272
|
+
const cwd = process.cwd();
|
|
2273
|
+
const types = [];
|
|
2274
|
+
if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) types.push('rust');
|
|
2275
|
+
if (fs.existsSync(path.join(cwd, 'package.json'))) types.push('node');
|
|
2276
|
+
if (fs.existsSync(path.join(cwd, 'requirements.txt')) || fs.existsSync(path.join(cwd, 'pyproject.toml'))) types.push('python');
|
|
2277
|
+
if (fs.existsSync(path.join(cwd, 'go.mod'))) types.push('go');
|
|
2278
|
+
if (fs.existsSync(path.join(cwd, 'Gemfile'))) types.push('ruby');
|
|
2279
|
+
if (fs.existsSync(path.join(cwd, 'pom.xml')) || fs.existsSync(path.join(cwd, 'build.gradle'))) types.push('java');
|
|
2280
|
+
return types.length > 0 ? types : ['generic'];
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
// Helper: Get permissions for project type
|
|
2284
|
+
function getPermissionsForProjectType(types) {
|
|
2285
|
+
const basePermissions = [
|
|
2286
|
+
'Bash(git status)', 'Bash(git diff:*)', 'Bash(git log:*)', 'Bash(git add:*)',
|
|
2287
|
+
'Bash(git commit:*)', 'Bash(git push)', 'Bash(git branch:*)', 'Bash(git checkout:*)',
|
|
2288
|
+
'Bash(ls:*)', 'Bash(pwd)', 'Bash(cat:*)', 'Bash(mkdir:*)', 'Bash(which:*)', 'Bash(ruvector:*)'
|
|
2289
|
+
];
|
|
2290
|
+
const typePermissions = {
|
|
2291
|
+
rust: ['Bash(cargo:*)', 'Bash(rustc:*)', 'Bash(rustfmt:*)', 'Bash(clippy:*)', 'Bash(wasm-pack:*)'],
|
|
2292
|
+
node: ['Bash(npm:*)', 'Bash(npx:*)', 'Bash(node:*)', 'Bash(yarn:*)', 'Bash(pnpm:*)'],
|
|
2293
|
+
python: ['Bash(python:*)', 'Bash(pip:*)', 'Bash(pytest:*)', 'Bash(poetry:*)', 'Bash(uv:*)'],
|
|
2294
|
+
go: ['Bash(go:*)', 'Bash(gofmt:*)'],
|
|
2295
|
+
ruby: ['Bash(ruby:*)', 'Bash(gem:*)', 'Bash(bundle:*)', 'Bash(rails:*)'],
|
|
2296
|
+
java: ['Bash(mvn:*)', 'Bash(gradle:*)', 'Bash(java:*)', 'Bash(javac:*)'],
|
|
2297
|
+
generic: ['Bash(make:*)']
|
|
2298
|
+
};
|
|
2299
|
+
let perms = [...basePermissions];
|
|
2300
|
+
types.forEach(t => { if (typePermissions[t]) perms = perms.concat(typePermissions[t]); });
|
|
2301
|
+
return [...new Set(perms)];
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
hooksCmd.command('init')
|
|
2305
|
+
.description('Initialize hooks in current project')
|
|
2306
|
+
.option('--force', 'Force overwrite existing settings')
|
|
2307
|
+
.option('--minimal', 'Only basic hooks (no env, permissions, or advanced hooks)')
|
|
2308
|
+
.option('--no-claude-md', 'Skip CLAUDE.md creation')
|
|
2309
|
+
.option('--no-permissions', 'Skip permissions configuration')
|
|
2310
|
+
.option('--no-env', 'Skip environment variables')
|
|
2311
|
+
.option('--no-gitignore', 'Skip .gitignore update')
|
|
2312
|
+
.option('--no-mcp', 'Skip MCP server configuration')
|
|
2313
|
+
.option('--no-statusline', 'Skip statusLine configuration')
|
|
2314
|
+
.action((opts) => {
|
|
2271
2315
|
const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
|
|
2272
2316
|
const settingsDir = path.dirname(settingsPath);
|
|
2273
2317
|
if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true });
|
|
@@ -2275,15 +2319,85 @@ hooksCmd.command('init').description('Initialize hooks in current project').opti
|
|
|
2275
2319
|
if (fs.existsSync(settingsPath) && !opts.force) {
|
|
2276
2320
|
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); } catch {}
|
|
2277
2321
|
}
|
|
2322
|
+
|
|
2278
2323
|
// Fix schema if present
|
|
2279
2324
|
if (settings.$schema) {
|
|
2280
2325
|
settings.$schema = 'https://json.schemastore.org/claude-code-settings.json';
|
|
2281
2326
|
}
|
|
2327
|
+
|
|
2282
2328
|
// Clean up invalid hook names
|
|
2283
2329
|
if (settings.hooks) {
|
|
2284
2330
|
if (settings.hooks.Start) { delete settings.hooks.Start; }
|
|
2285
2331
|
if (settings.hooks.End) { delete settings.hooks.End; }
|
|
2286
2332
|
}
|
|
2333
|
+
|
|
2334
|
+
// Detect project type
|
|
2335
|
+
const projectTypes = detectProjectType();
|
|
2336
|
+
console.log(chalk.blue(` ✓ Detected project type(s): ${projectTypes.join(', ')}`));
|
|
2337
|
+
|
|
2338
|
+
// Environment variables for intelligence (unless --minimal or --no-env)
|
|
2339
|
+
if (!opts.minimal && opts.env !== false) {
|
|
2340
|
+
settings.env = settings.env || {};
|
|
2341
|
+
settings.env.RUVECTOR_INTELLIGENCE_ENABLED = settings.env.RUVECTOR_INTELLIGENCE_ENABLED || 'true';
|
|
2342
|
+
settings.env.RUVECTOR_LEARNING_RATE = settings.env.RUVECTOR_LEARNING_RATE || '0.1';
|
|
2343
|
+
settings.env.RUVECTOR_MEMORY_BACKEND = settings.env.RUVECTOR_MEMORY_BACKEND || 'rvlite';
|
|
2344
|
+
settings.env.INTELLIGENCE_MODE = settings.env.INTELLIGENCE_MODE || 'treatment';
|
|
2345
|
+
console.log(chalk.blue(' ✓ Environment variables configured'));
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
// Permissions based on detected project type (unless --minimal or --no-permissions)
|
|
2349
|
+
if (!opts.minimal && opts.permissions !== false) {
|
|
2350
|
+
settings.permissions = settings.permissions || {};
|
|
2351
|
+
settings.permissions.allow = settings.permissions.allow || getPermissionsForProjectType(projectTypes);
|
|
2352
|
+
settings.permissions.deny = settings.permissions.deny || [
|
|
2353
|
+
'Bash(rm -rf /)',
|
|
2354
|
+
'Bash(sudo rm:*)',
|
|
2355
|
+
'Bash(chmod 777:*)',
|
|
2356
|
+
'Bash(:(){ :|:& };:)'
|
|
2357
|
+
];
|
|
2358
|
+
console.log(chalk.blue(' ✓ Permissions configured (project-specific)'));
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
// MCP server configuration (unless --minimal or --no-mcp)
|
|
2362
|
+
if (!opts.minimal && opts.mcp !== false) {
|
|
2363
|
+
settings.mcpServers = settings.mcpServers || {};
|
|
2364
|
+
// Only add if not already configured
|
|
2365
|
+
if (!settings.mcpServers['claude-flow'] && !settings.enabledMcpjsonServers?.includes('claude-flow')) {
|
|
2366
|
+
settings.enabledMcpjsonServers = settings.enabledMcpjsonServers || [];
|
|
2367
|
+
if (!settings.enabledMcpjsonServers.includes('claude-flow')) {
|
|
2368
|
+
settings.enabledMcpjsonServers.push('claude-flow');
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
console.log(chalk.blue(' ✓ MCP servers configured'));
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
// StatusLine configuration (unless --minimal or --no-statusline)
|
|
2375
|
+
if (!opts.minimal && opts.statusline !== false) {
|
|
2376
|
+
if (!settings.statusLine) {
|
|
2377
|
+
// Create a simple statusline script
|
|
2378
|
+
const statuslineScript = path.join(settingsDir, 'statusline.sh');
|
|
2379
|
+
const statuslineContent = `#!/bin/bash
|
|
2380
|
+
# RuVector Status Line - shows intelligence stats
|
|
2381
|
+
INTEL_FILE=".ruvector/intelligence.json"
|
|
2382
|
+
if [ -f "$INTEL_FILE" ]; then
|
|
2383
|
+
PATTERNS=$(jq -r '.patterns | length // 0' "$INTEL_FILE" 2>/dev/null || echo "0")
|
|
2384
|
+
MEMORIES=$(jq -r '.memories | length // 0' "$INTEL_FILE" 2>/dev/null || echo "0")
|
|
2385
|
+
echo "🧠 $PATTERNS patterns | 💾 $MEMORIES memories"
|
|
2386
|
+
else
|
|
2387
|
+
echo "🧠 RuVector"
|
|
2388
|
+
fi
|
|
2389
|
+
`;
|
|
2390
|
+
fs.writeFileSync(statuslineScript, statuslineContent);
|
|
2391
|
+
fs.chmodSync(statuslineScript, '755');
|
|
2392
|
+
settings.statusLine = {
|
|
2393
|
+
type: 'command',
|
|
2394
|
+
command: '.claude/statusline.sh'
|
|
2395
|
+
};
|
|
2396
|
+
console.log(chalk.blue(' ✓ StatusLine configured'));
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// Core hooks (always included)
|
|
2287
2401
|
settings.hooks = settings.hooks || {};
|
|
2288
2402
|
settings.hooks.PreToolUse = [
|
|
2289
2403
|
{ matcher: 'Edit|Write|MultiEdit', hooks: [{ type: 'command', command: 'npx ruvector hooks pre-edit "$TOOL_INPUT_file_path"' }] },
|
|
@@ -2295,53 +2409,135 @@ hooksCmd.command('init').description('Initialize hooks in current project').opti
|
|
|
2295
2409
|
];
|
|
2296
2410
|
settings.hooks.SessionStart = [{ hooks: [{ type: 'command', command: 'npx ruvector hooks session-start' }] }];
|
|
2297
2411
|
settings.hooks.Stop = [{ hooks: [{ type: 'command', command: 'npx ruvector hooks session-end' }] }];
|
|
2412
|
+
console.log(chalk.blue(' ✓ Core hooks (PreToolUse, PostToolUse, SessionStart, Stop)'));
|
|
2413
|
+
|
|
2414
|
+
// Advanced hooks (unless --minimal)
|
|
2415
|
+
if (!opts.minimal) {
|
|
2416
|
+
// UserPromptSubmit - context suggestions on each prompt
|
|
2417
|
+
settings.hooks.UserPromptSubmit = [{
|
|
2418
|
+
hooks: [{
|
|
2419
|
+
type: 'command',
|
|
2420
|
+
timeout: 2000,
|
|
2421
|
+
command: 'npx ruvector hooks suggest-context'
|
|
2422
|
+
}]
|
|
2423
|
+
}];
|
|
2424
|
+
|
|
2425
|
+
// PreCompact - preserve important context before compaction
|
|
2426
|
+
settings.hooks.PreCompact = [
|
|
2427
|
+
{
|
|
2428
|
+
matcher: 'auto',
|
|
2429
|
+
hooks: [{
|
|
2430
|
+
type: 'command',
|
|
2431
|
+
timeout: 3000,
|
|
2432
|
+
command: 'npx ruvector hooks pre-compact --auto'
|
|
2433
|
+
}]
|
|
2434
|
+
},
|
|
2435
|
+
{
|
|
2436
|
+
matcher: 'manual',
|
|
2437
|
+
hooks: [{
|
|
2438
|
+
type: 'command',
|
|
2439
|
+
timeout: 3000,
|
|
2440
|
+
command: 'npx ruvector hooks pre-compact'
|
|
2441
|
+
}]
|
|
2442
|
+
}
|
|
2443
|
+
];
|
|
2444
|
+
|
|
2445
|
+
// Notification - track all notifications for learning
|
|
2446
|
+
settings.hooks.Notification = [{
|
|
2447
|
+
matcher: '.*',
|
|
2448
|
+
hooks: [{
|
|
2449
|
+
type: 'command',
|
|
2450
|
+
timeout: 1000,
|
|
2451
|
+
command: 'npx ruvector hooks track-notification'
|
|
2452
|
+
}]
|
|
2453
|
+
}];
|
|
2454
|
+
console.log(chalk.blue(' ✓ Advanced hooks (UserPromptSubmit, PreCompact, Notification)'));
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2298
2457
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
2299
|
-
console.log(chalk.green('✅ Hooks initialized in .claude/settings.json'));
|
|
2458
|
+
console.log(chalk.green('\n✅ Hooks initialized in .claude/settings.json'));
|
|
2300
2459
|
|
|
2301
2460
|
// Create CLAUDE.md if it doesn't exist (or force)
|
|
2302
2461
|
const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
|
|
2303
2462
|
if (opts.claudeMd !== false && (!fs.existsSync(claudeMdPath) || opts.force)) {
|
|
2304
2463
|
const claudeMdContent = `# Claude Code Project Configuration
|
|
2305
2464
|
|
|
2306
|
-
## RuVector Self-Learning
|
|
2465
|
+
## RuVector Self-Learning Intelligence
|
|
2307
2466
|
|
|
2308
|
-
This project uses RuVector's self-learning intelligence hooks for enhanced AI-assisted development.
|
|
2467
|
+
This project uses RuVector's self-learning intelligence hooks for enhanced AI-assisted development with Q-learning, vector memory, and automatic agent routing.
|
|
2309
2468
|
|
|
2310
2469
|
### Active Hooks
|
|
2311
2470
|
|
|
2312
2471
|
| Hook | Trigger | Purpose |
|
|
2313
2472
|
|------|---------|---------|
|
|
2314
|
-
| PreToolUse | Before Edit/Write/Bash | Agent routing,
|
|
2315
|
-
| PostToolUse | After Edit/Write/Bash | Q-learning update, pattern recording |
|
|
2316
|
-
| SessionStart | Conversation begins | Load intelligence, display stats |
|
|
2317
|
-
| Stop | Conversation ends | Save learning data |
|
|
2473
|
+
| **PreToolUse** | Before Edit/Write/Bash | Agent routing, file analysis, command risk assessment |
|
|
2474
|
+
| **PostToolUse** | After Edit/Write/Bash | Q-learning update, pattern recording, outcome tracking |
|
|
2475
|
+
| **SessionStart** | Conversation begins | Load intelligence state, display learning stats |
|
|
2476
|
+
| **Stop** | Conversation ends | Save learning data, export metrics |
|
|
2477
|
+
| **UserPromptSubmit** | User sends message | Context suggestions, pattern recommendations |
|
|
2478
|
+
| **PreCompact** | Before context compaction | Preserve important context and memories |
|
|
2479
|
+
| **Notification** | Any notification | Track events for learning |
|
|
2480
|
+
|
|
2481
|
+
### Environment Variables
|
|
2482
|
+
|
|
2483
|
+
| Variable | Default | Description |
|
|
2484
|
+
|----------|---------|-------------|
|
|
2485
|
+
| \`RUVECTOR_INTELLIGENCE_ENABLED\` | \`true\` | Enable/disable intelligence layer |
|
|
2486
|
+
| \`RUVECTOR_LEARNING_RATE\` | \`0.1\` | Q-learning rate (0.0-1.0) |
|
|
2487
|
+
| \`RUVECTOR_MEMORY_BACKEND\` | \`rvlite\` | Memory storage backend |
|
|
2488
|
+
| \`INTELLIGENCE_MODE\` | \`treatment\` | A/B testing mode (treatment/control) |
|
|
2318
2489
|
|
|
2319
2490
|
### Commands
|
|
2320
2491
|
|
|
2321
2492
|
\`\`\`bash
|
|
2493
|
+
# Initialize hooks in a project
|
|
2494
|
+
npx ruvector hooks init
|
|
2495
|
+
|
|
2322
2496
|
# View learning statistics
|
|
2323
2497
|
npx ruvector hooks stats
|
|
2324
2498
|
|
|
2325
2499
|
# Route a task to best agent
|
|
2326
2500
|
npx ruvector hooks route "implement feature X"
|
|
2327
2501
|
|
|
2328
|
-
# Store context in memory
|
|
2502
|
+
# Store context in vector memory
|
|
2329
2503
|
npx ruvector hooks remember "important context" -t project
|
|
2330
2504
|
|
|
2331
|
-
# Recall from memory
|
|
2505
|
+
# Recall from memory (semantic search)
|
|
2332
2506
|
npx ruvector hooks recall "context query"
|
|
2507
|
+
|
|
2508
|
+
# Manual session management
|
|
2509
|
+
npx ruvector hooks session-start
|
|
2510
|
+
npx ruvector hooks session-end
|
|
2333
2511
|
\`\`\`
|
|
2334
2512
|
|
|
2335
2513
|
### How It Works
|
|
2336
2514
|
|
|
2337
|
-
1. **Pre-edit hooks** analyze files and suggest the best agent
|
|
2515
|
+
1. **Pre-edit hooks** analyze files and suggest the best agent based on learned patterns
|
|
2338
2516
|
2. **Post-edit hooks** record outcomes to improve future suggestions via Q-learning
|
|
2339
|
-
3. **Memory hooks** store and retrieve context using vector embeddings
|
|
2517
|
+
3. **Memory hooks** store and retrieve context using vector embeddings (cosine similarity)
|
|
2340
2518
|
4. **Session hooks** manage learning state across conversations
|
|
2519
|
+
5. **UserPromptSubmit** provides context suggestions on each message
|
|
2520
|
+
6. **PreCompact** preserves critical context before conversation compaction
|
|
2521
|
+
7. **Notification** tracks all events for continuous learning
|
|
2522
|
+
|
|
2523
|
+
### Learning Data
|
|
2524
|
+
|
|
2525
|
+
Stored in \`.ruvector/intelligence.json\`:
|
|
2526
|
+
- **Q-table patterns**: State-action values for agent routing
|
|
2527
|
+
- **Vector memories**: Embeddings for semantic recall
|
|
2528
|
+
- **Trajectories**: Learning history for improvement tracking
|
|
2529
|
+
- **Error patterns**: Known issues and suggested fixes
|
|
2341
2530
|
|
|
2342
|
-
###
|
|
2531
|
+
### Init Options
|
|
2343
2532
|
|
|
2344
|
-
|
|
2533
|
+
\`\`\`bash
|
|
2534
|
+
npx ruvector hooks init # Full configuration
|
|
2535
|
+
npx ruvector hooks init --minimal # Basic hooks only
|
|
2536
|
+
npx ruvector hooks init --no-env # Skip environment variables
|
|
2537
|
+
npx ruvector hooks init --no-permissions # Skip permissions
|
|
2538
|
+
npx ruvector hooks init --no-claude-md # Skip this file
|
|
2539
|
+
npx ruvector hooks init --force # Overwrite existing
|
|
2540
|
+
\`\`\`
|
|
2345
2541
|
|
|
2346
2542
|
---
|
|
2347
2543
|
*Powered by [RuVector](https://github.com/ruvnet/ruvector) self-learning intelligence*
|
|
@@ -2351,6 +2547,32 @@ Settings are stored in \`.claude/settings.json\`. Run \`npx ruvector hooks init\
|
|
|
2351
2547
|
} else if (fs.existsSync(claudeMdPath) && !opts.force) {
|
|
2352
2548
|
console.log(chalk.yellow('ℹ️ CLAUDE.md already exists (use --force to overwrite)'));
|
|
2353
2549
|
}
|
|
2550
|
+
|
|
2551
|
+
// Update .gitignore (unless --no-gitignore)
|
|
2552
|
+
if (opts.gitignore !== false) {
|
|
2553
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
2554
|
+
const entriesToAdd = ['.ruvector/', '.claude/statusline.sh'];
|
|
2555
|
+
let gitignoreContent = '';
|
|
2556
|
+
if (fs.existsSync(gitignorePath)) {
|
|
2557
|
+
gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
|
|
2558
|
+
}
|
|
2559
|
+
const linesToAdd = entriesToAdd.filter(entry => !gitignoreContent.includes(entry));
|
|
2560
|
+
if (linesToAdd.length > 0) {
|
|
2561
|
+
const newContent = gitignoreContent.trim() + '\n\n# RuVector intelligence data\n' + linesToAdd.join('\n') + '\n';
|
|
2562
|
+
fs.writeFileSync(gitignorePath, newContent);
|
|
2563
|
+
console.log(chalk.blue(' ✓ .gitignore updated'));
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
// Create .ruvector directory for intelligence data
|
|
2568
|
+
const ruvectorDir = path.join(process.cwd(), '.ruvector');
|
|
2569
|
+
if (!fs.existsSync(ruvectorDir)) {
|
|
2570
|
+
fs.mkdirSync(ruvectorDir, { recursive: true });
|
|
2571
|
+
console.log(chalk.blue(' ✓ .ruvector/ directory created'));
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
console.log(chalk.green('\n✅ RuVector hooks initialization complete!'));
|
|
2575
|
+
console.log(chalk.dim(' Run `npx ruvector hooks verify` to test the setup'));
|
|
2354
2576
|
});
|
|
2355
2577
|
|
|
2356
2578
|
hooksCmd.command('stats').description('Show intelligence statistics').action(() => {
|
|
@@ -2487,4 +2709,291 @@ hooksCmd.command('track-notification').description('Track notification').action(
|
|
|
2487
2709
|
console.log(JSON.stringify({ tracked: true }));
|
|
2488
2710
|
});
|
|
2489
2711
|
|
|
2712
|
+
// Verify hooks are working
|
|
2713
|
+
hooksCmd.command('verify')
|
|
2714
|
+
.description('Verify hooks are working correctly')
|
|
2715
|
+
.option('--verbose', 'Show detailed output')
|
|
2716
|
+
.action((opts) => {
|
|
2717
|
+
console.log(chalk.bold.cyan('\n🔍 RuVector Hooks Verification\n'));
|
|
2718
|
+
const checks = [];
|
|
2719
|
+
|
|
2720
|
+
// Check 1: Settings file exists
|
|
2721
|
+
const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
|
|
2722
|
+
if (fs.existsSync(settingsPath)) {
|
|
2723
|
+
checks.push({ name: 'Settings file', status: 'pass', detail: '.claude/settings.json exists' });
|
|
2724
|
+
try {
|
|
2725
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
2726
|
+
// Check hooks
|
|
2727
|
+
const requiredHooks = ['PreToolUse', 'PostToolUse', 'SessionStart', 'Stop'];
|
|
2728
|
+
const missingHooks = requiredHooks.filter(h => !settings.hooks?.[h]);
|
|
2729
|
+
if (missingHooks.length === 0) {
|
|
2730
|
+
checks.push({ name: 'Required hooks', status: 'pass', detail: 'All core hooks configured' });
|
|
2731
|
+
} else {
|
|
2732
|
+
checks.push({ name: 'Required hooks', status: 'fail', detail: `Missing: ${missingHooks.join(', ')}` });
|
|
2733
|
+
}
|
|
2734
|
+
// Check advanced hooks
|
|
2735
|
+
const advancedHooks = ['UserPromptSubmit', 'PreCompact', 'Notification'];
|
|
2736
|
+
const hasAdvanced = advancedHooks.filter(h => settings.hooks?.[h]);
|
|
2737
|
+
if (hasAdvanced.length > 0) {
|
|
2738
|
+
checks.push({ name: 'Advanced hooks', status: 'pass', detail: `${hasAdvanced.length}/3 configured` });
|
|
2739
|
+
} else {
|
|
2740
|
+
checks.push({ name: 'Advanced hooks', status: 'warn', detail: 'None configured (optional)' });
|
|
2741
|
+
}
|
|
2742
|
+
// Check env
|
|
2743
|
+
if (settings.env?.RUVECTOR_INTELLIGENCE_ENABLED) {
|
|
2744
|
+
checks.push({ name: 'Environment vars', status: 'pass', detail: 'Intelligence enabled' });
|
|
2745
|
+
} else {
|
|
2746
|
+
checks.push({ name: 'Environment vars', status: 'warn', detail: 'Not configured' });
|
|
2747
|
+
}
|
|
2748
|
+
// Check permissions
|
|
2749
|
+
if (settings.permissions?.allow?.length > 0) {
|
|
2750
|
+
checks.push({ name: 'Permissions', status: 'pass', detail: `${settings.permissions.allow.length} allowed patterns` });
|
|
2751
|
+
} else {
|
|
2752
|
+
checks.push({ name: 'Permissions', status: 'warn', detail: 'Not configured' });
|
|
2753
|
+
}
|
|
2754
|
+
} catch (e) {
|
|
2755
|
+
checks.push({ name: 'Settings parse', status: 'fail', detail: 'Invalid JSON' });
|
|
2756
|
+
}
|
|
2757
|
+
} else {
|
|
2758
|
+
checks.push({ name: 'Settings file', status: 'fail', detail: 'Run `npx ruvector hooks init` first' });
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
// Check 2: .ruvector directory
|
|
2762
|
+
const ruvectorDir = path.join(process.cwd(), '.ruvector');
|
|
2763
|
+
if (fs.existsSync(ruvectorDir)) {
|
|
2764
|
+
checks.push({ name: 'Data directory', status: 'pass', detail: '.ruvector/ exists' });
|
|
2765
|
+
const intelFile = path.join(ruvectorDir, 'intelligence.json');
|
|
2766
|
+
if (fs.existsSync(intelFile)) {
|
|
2767
|
+
const stats = fs.statSync(intelFile);
|
|
2768
|
+
checks.push({ name: 'Intelligence file', status: 'pass', detail: `${(stats.size / 1024).toFixed(1)}KB` });
|
|
2769
|
+
} else {
|
|
2770
|
+
checks.push({ name: 'Intelligence file', status: 'warn', detail: 'Will be created on first use' });
|
|
2771
|
+
}
|
|
2772
|
+
} else {
|
|
2773
|
+
checks.push({ name: 'Data directory', status: 'warn', detail: 'Will be created on first use' });
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
// Check 3: Hook command execution
|
|
2777
|
+
try {
|
|
2778
|
+
const { execSync } = require('child_process');
|
|
2779
|
+
execSync('npx ruvector hooks stats', { stdio: 'pipe', timeout: 5000 });
|
|
2780
|
+
checks.push({ name: 'Command execution', status: 'pass', detail: 'Hooks commands work' });
|
|
2781
|
+
} catch (e) {
|
|
2782
|
+
checks.push({ name: 'Command execution', status: 'fail', detail: 'Commands failed to execute' });
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
// Display results
|
|
2786
|
+
let passCount = 0, warnCount = 0, failCount = 0;
|
|
2787
|
+
checks.forEach(c => {
|
|
2788
|
+
const icon = c.status === 'pass' ? chalk.green('✓') : c.status === 'warn' ? chalk.yellow('⚠') : chalk.red('✗');
|
|
2789
|
+
const statusColor = c.status === 'pass' ? chalk.green : c.status === 'warn' ? chalk.yellow : chalk.red;
|
|
2790
|
+
console.log(` ${icon} ${c.name}: ${statusColor(c.detail)}`);
|
|
2791
|
+
if (c.status === 'pass') passCount++;
|
|
2792
|
+
else if (c.status === 'warn') warnCount++;
|
|
2793
|
+
else failCount++;
|
|
2794
|
+
});
|
|
2795
|
+
|
|
2796
|
+
console.log('');
|
|
2797
|
+
if (failCount === 0) {
|
|
2798
|
+
console.log(chalk.green(`✅ Verification passed! ${passCount} checks passed, ${warnCount} warnings`));
|
|
2799
|
+
} else {
|
|
2800
|
+
console.log(chalk.red(`❌ Verification failed: ${failCount} issues found`));
|
|
2801
|
+
console.log(chalk.dim(' Run `npx ruvector hooks doctor` for detailed diagnostics'));
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
2804
|
+
|
|
2805
|
+
// Doctor - diagnose setup issues
|
|
2806
|
+
hooksCmd.command('doctor')
|
|
2807
|
+
.description('Diagnose and fix setup issues')
|
|
2808
|
+
.option('--fix', 'Automatically fix issues')
|
|
2809
|
+
.action((opts) => {
|
|
2810
|
+
console.log(chalk.bold.cyan('\n🩺 RuVector Hooks Doctor\n'));
|
|
2811
|
+
const issues = [];
|
|
2812
|
+
const fixes = [];
|
|
2813
|
+
|
|
2814
|
+
// Check settings file
|
|
2815
|
+
const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
|
|
2816
|
+
if (!fs.existsSync(settingsPath)) {
|
|
2817
|
+
issues.push({ severity: 'error', message: 'No .claude/settings.json found', fix: 'Run `npx ruvector hooks init`' });
|
|
2818
|
+
} else {
|
|
2819
|
+
try {
|
|
2820
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
2821
|
+
|
|
2822
|
+
// Check for invalid schema
|
|
2823
|
+
if (settings.$schema && !settings.$schema.includes('schemastore.org')) {
|
|
2824
|
+
issues.push({ severity: 'warning', message: 'Invalid schema URL', fix: 'Will be corrected' });
|
|
2825
|
+
if (opts.fix) {
|
|
2826
|
+
settings.$schema = 'https://json.schemastore.org/claude-code-settings.json';
|
|
2827
|
+
fixes.push('Fixed schema URL');
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
// Check for old hook names
|
|
2832
|
+
if (settings.hooks?.Start || settings.hooks?.End) {
|
|
2833
|
+
issues.push({ severity: 'error', message: 'Invalid hook names (Start/End)', fix: 'Should be SessionStart/Stop' });
|
|
2834
|
+
if (opts.fix) {
|
|
2835
|
+
delete settings.hooks.Start;
|
|
2836
|
+
delete settings.hooks.End;
|
|
2837
|
+
fixes.push('Removed invalid hook names');
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// Check hook format
|
|
2842
|
+
const hookNames = ['PreToolUse', 'PostToolUse'];
|
|
2843
|
+
hookNames.forEach(name => {
|
|
2844
|
+
if (settings.hooks?.[name]) {
|
|
2845
|
+
settings.hooks[name].forEach((hook, i) => {
|
|
2846
|
+
if (typeof hook.matcher === 'object') {
|
|
2847
|
+
issues.push({ severity: 'error', message: `${name}[${i}].matcher should be string, not object`, fix: 'Will be corrected' });
|
|
2848
|
+
}
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
});
|
|
2852
|
+
|
|
2853
|
+
// Check for npx vs direct command
|
|
2854
|
+
const checkCommands = (hooks) => {
|
|
2855
|
+
if (!hooks) return;
|
|
2856
|
+
hooks.forEach(h => {
|
|
2857
|
+
h.hooks?.forEach(hh => {
|
|
2858
|
+
if (hh.command && hh.command.includes('ruvector') && !hh.command.startsWith('npx ') && !hh.command.includes('/bin/')) {
|
|
2859
|
+
issues.push({ severity: 'warning', message: `Command should use 'npx ruvector' for portability`, fix: 'Update to use npx' });
|
|
2860
|
+
}
|
|
2861
|
+
});
|
|
2862
|
+
});
|
|
2863
|
+
};
|
|
2864
|
+
Object.values(settings.hooks || {}).forEach(checkCommands);
|
|
2865
|
+
|
|
2866
|
+
// Save fixes
|
|
2867
|
+
if (opts.fix && fixes.length > 0) {
|
|
2868
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
2869
|
+
}
|
|
2870
|
+
} catch (e) {
|
|
2871
|
+
issues.push({ severity: 'error', message: 'Invalid JSON in settings file', fix: 'Re-run `npx ruvector hooks init --force`' });
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
// Check .gitignore
|
|
2876
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
2877
|
+
if (fs.existsSync(gitignorePath)) {
|
|
2878
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
2879
|
+
if (!content.includes('.ruvector/')) {
|
|
2880
|
+
issues.push({ severity: 'warning', message: '.ruvector/ not in .gitignore', fix: 'Add to prevent committing learning data' });
|
|
2881
|
+
if (opts.fix) {
|
|
2882
|
+
fs.appendFileSync(gitignorePath, '\n# RuVector intelligence data\n.ruvector/\n');
|
|
2883
|
+
fixes.push('Added .ruvector/ to .gitignore');
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// Display results
|
|
2889
|
+
if (issues.length === 0) {
|
|
2890
|
+
console.log(chalk.green(' ✓ No issues found! Your setup looks healthy.'));
|
|
2891
|
+
} else {
|
|
2892
|
+
issues.forEach(i => {
|
|
2893
|
+
const icon = i.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
|
|
2894
|
+
console.log(` ${icon} ${i.message}`);
|
|
2895
|
+
console.log(chalk.dim(` Fix: ${i.fix}`));
|
|
2896
|
+
});
|
|
2897
|
+
|
|
2898
|
+
if (opts.fix && fixes.length > 0) {
|
|
2899
|
+
console.log(chalk.green(`\n✅ Applied ${fixes.length} fix(es):`));
|
|
2900
|
+
fixes.forEach(f => console.log(chalk.green(` • ${f}`)));
|
|
2901
|
+
} else if (issues.some(i => i.severity === 'error')) {
|
|
2902
|
+
console.log(chalk.yellow('\n💡 Run with --fix to automatically fix issues'));
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
|
|
2907
|
+
// Export intelligence data
|
|
2908
|
+
hooksCmd.command('export')
|
|
2909
|
+
.description('Export intelligence data for backup')
|
|
2910
|
+
.option('-o, --output <file>', 'Output file path', 'ruvector-export.json')
|
|
2911
|
+
.option('--include-all', 'Include all data (patterns, memories, trajectories)')
|
|
2912
|
+
.action((opts) => {
|
|
2913
|
+
const intel = new Intelligence();
|
|
2914
|
+
const exportData = {
|
|
2915
|
+
version: '1.0',
|
|
2916
|
+
exported_at: new Date().toISOString(),
|
|
2917
|
+
patterns: intel.data?.patterns || {},
|
|
2918
|
+
memories: opts.includeAll ? (intel.data?.memories || []) : [],
|
|
2919
|
+
trajectories: opts.includeAll ? (intel.data?.trajectories || []) : [],
|
|
2920
|
+
errors: intel.data?.errors || {},
|
|
2921
|
+
stats: intel.stats()
|
|
2922
|
+
};
|
|
2923
|
+
|
|
2924
|
+
const outputPath = path.resolve(opts.output);
|
|
2925
|
+
fs.writeFileSync(outputPath, JSON.stringify(exportData, null, 2));
|
|
2926
|
+
|
|
2927
|
+
console.log(chalk.green(`✅ Exported intelligence data to ${outputPath}`));
|
|
2928
|
+
console.log(chalk.dim(` ${Object.keys(exportData.patterns).length} patterns`));
|
|
2929
|
+
console.log(chalk.dim(` ${exportData.memories.length} memories`));
|
|
2930
|
+
console.log(chalk.dim(` ${exportData.trajectories.length} trajectories`));
|
|
2931
|
+
});
|
|
2932
|
+
|
|
2933
|
+
// Import intelligence data
|
|
2934
|
+
hooksCmd.command('import')
|
|
2935
|
+
.description('Import intelligence data from backup')
|
|
2936
|
+
.argument('<file>', 'Import file path')
|
|
2937
|
+
.option('--merge', 'Merge with existing data (default: replace)')
|
|
2938
|
+
.option('--dry-run', 'Show what would be imported without making changes')
|
|
2939
|
+
.action((file, opts) => {
|
|
2940
|
+
const importPath = path.resolve(file);
|
|
2941
|
+
if (!fs.existsSync(importPath)) {
|
|
2942
|
+
console.error(chalk.red(`❌ File not found: ${importPath}`));
|
|
2943
|
+
process.exit(1);
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
try {
|
|
2947
|
+
const importData = JSON.parse(fs.readFileSync(importPath, 'utf-8'));
|
|
2948
|
+
|
|
2949
|
+
if (!importData.version) {
|
|
2950
|
+
console.error(chalk.red('❌ Invalid export file (missing version)'));
|
|
2951
|
+
process.exit(1);
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
console.log(chalk.cyan(`📦 Import file: ${file}`));
|
|
2955
|
+
console.log(chalk.dim(` Version: ${importData.version}`));
|
|
2956
|
+
console.log(chalk.dim(` Exported: ${importData.exported_at}`));
|
|
2957
|
+
console.log(chalk.dim(` Patterns: ${Object.keys(importData.patterns || {}).length}`));
|
|
2958
|
+
console.log(chalk.dim(` Memories: ${(importData.memories || []).length}`));
|
|
2959
|
+
console.log(chalk.dim(` Trajectories: ${(importData.trajectories || []).length}`));
|
|
2960
|
+
|
|
2961
|
+
if (opts.dryRun) {
|
|
2962
|
+
console.log(chalk.yellow('\n⚠️ Dry run - no changes made'));
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
const intel = new Intelligence();
|
|
2967
|
+
|
|
2968
|
+
if (opts.merge) {
|
|
2969
|
+
// Merge patterns
|
|
2970
|
+
Object.assign(intel.data.patterns, importData.patterns || {});
|
|
2971
|
+
// Merge memories (deduplicate by content)
|
|
2972
|
+
const existingContent = new Set((intel.data.memories || []).map(m => m.content));
|
|
2973
|
+
(importData.memories || []).forEach(m => {
|
|
2974
|
+
if (!existingContent.has(m.content)) {
|
|
2975
|
+
intel.data.memories.push(m);
|
|
2976
|
+
}
|
|
2977
|
+
});
|
|
2978
|
+
// Merge trajectories
|
|
2979
|
+
intel.data.trajectories = (intel.data.trajectories || []).concat(importData.trajectories || []);
|
|
2980
|
+
// Merge errors
|
|
2981
|
+
Object.assign(intel.data.errors, importData.errors || {});
|
|
2982
|
+
console.log(chalk.green('✅ Merged intelligence data'));
|
|
2983
|
+
} else {
|
|
2984
|
+
intel.data.patterns = importData.patterns || {};
|
|
2985
|
+
intel.data.memories = importData.memories || [];
|
|
2986
|
+
intel.data.trajectories = importData.trajectories || [];
|
|
2987
|
+
intel.data.errors = importData.errors || {};
|
|
2988
|
+
console.log(chalk.green('✅ Replaced intelligence data'));
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
intel.save();
|
|
2992
|
+
console.log(chalk.dim(' Data saved to .ruvector/intelligence.json'));
|
|
2993
|
+
} catch (e) {
|
|
2994
|
+
console.error(chalk.red(`❌ Failed to import: ${e.message}`));
|
|
2995
|
+
process.exit(1);
|
|
2996
|
+
}
|
|
2997
|
+
});
|
|
2998
|
+
|
|
2490
2999
|
program.parse();
|