UNPKG

automagik-genie

Version:

Self-evolving AI agent orchestration framework with Model Context Protocol support

223 lines (195 loc) 9 kB
#!/usr/bin/env node const { spawnSync, execSync } = require('child_process'); const path = require('path'); // Get git root directory (works from .git/hooks/) const gitRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim(); function run(script) { const p = path.join(gitRoot, '.genie', 'scripts', script); const res = spawnSync('node', [p], { stdio: 'inherit' }); return res.status || 0; } // TODO: Re-enable when `genie run` CLI is stable (currently causes instability in hooks) // function runGenie(agent, prompt) { // // Run Genie workflow and capture session ID + wait for completion // const { execSync } = require('child_process'); // const fs = require('fs'); // // try { // const genieCliPath = path.join(gitRoot, '.genie', 'cli', 'dist', 'genie-cli.js'); // if (!fs.existsSync(genieCliPath)) { // return { sessionId: null, success: true, message: 'Genie CLI not built (skipping workflow)' }; // } // // console.log(`Running Genie workflow: ${agent}...`); // // // Start workflow (fire and forget, non-blocking) // const spawn = require('child_process').spawn; // const proc = spawn('node', [genieCliPath, 'run', agent, prompt], { // cwd: gitRoot, // stdio: 'ignore', // detached: true // }); // proc.unref(); // // // Return success - workflow runs in background // return { sessionId: null, success: true, message: `Workflow ${agent} started (runs in background)` }; // } catch (e) { // console.log(`⚠️ Could not run workflow: ${e.message}`); // return { sessionId: null, success: true, message: 'Workflow skipped' }; // } // } // Check if commit only contains files that don't require full hook validation function shouldSkipHooks() { try { const stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf8' }).trim().split('\n').filter(Boolean); if (stagedFiles.length === 0) return false; // ============================================================================== // SKIP PATTERNS (expand this list as needed for performance optimization) // ============================================================================== // Pattern 1: .genie/wishes/*.md - Wish documents that don't affect codebase // Pattern 2: [FUTURE] Documentation-only commits // Pattern 3: [FUTURE] Test fixtures or mock data // ============================================================================== const skipPatterns = [ // Pattern 1: Wish files only (file) => file.startsWith('.genie/wishes/') && file.endsWith('.md'), // Add more patterns here as needed: // (file) => file.startsWith('.genie/docs/') && file.endsWith('.md'), // (file) => file.startsWith('test/fixtures/') && file.endsWith('.json'), ]; // Skip hooks if ALL staged files match at least one skip pattern const allFilesSkippable = stagedFiles.every(file => skipPatterns.some(pattern => pattern(file)) ); return allFilesSkippable; } catch (e) { return false; // On error, run hooks normally } } // Timing utility function timeExecution(label, fn) { const start = Date.now(); const result = fn(); const duration = Date.now() - start; console.log(` ⏱️ ${label}: ${duration}ms`); return result; } function main() { const totalStart = Date.now(); console.log('## Pre-Commit'); // Early exit for forge worktrees (total isolation, full performance) try { const gitDir = execSync('git rev-parse --git-dir', { encoding: 'utf8' }).trim(); const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim(); const isWorktree = gitDir.includes('/worktrees/'); const isForgeBranch = branch.startsWith('forge/'); if (isForgeBranch || isWorktree) { console.log('🔧 Forge worktree detected - skipping ALL hooks (full performance mode)'); console.log(` Branch: ${branch}`); console.log('- Result: ✅ Pre-commit validations skipped (forge isolation)'); process.exit(0); } } catch (e) { // Continue with normal hooks on error } // Early exit for .genie/wishes/*.md only commits if (shouldSkipHooks()) { console.log('✨ Fast-path: Only wish files detected, skipping hooks'); console.log('- Result: ✅ Pre-commit validations skipped (wish-only commit)'); process.exit(0); } let exitCode = 0; const validations = [ 'validate-user-files-not-committed.cjs', 'validate-cross-references.cjs', 'forge-task-link.cjs', // Auto-link Forge tasks to wishes on first commit 'validate-mcp-build.cjs', // Ensure MCP dist files are in sync with source ]; // Security validation (blocking) console.log('🔐 Checking for secrets in staged files...'); const checkSecretsPath = path.join(gitRoot, '.genie', 'scripts', 'helpers', 'check-secrets.js'); const secretsCheckCode = timeExecution('Secret detection', () => { const secretsCheck = spawnSync('node', [checkSecretsPath, '--staged'], { stdio: 'inherit' }); return secretsCheck.status || 0; }); if (secretsCheckCode !== 0) { exitCode = 1; } // Path validation (blocking) console.log('🔗 Validating file path references...'); const validatePathsPath = path.join(gitRoot, '.genie', 'scripts', 'helpers', 'validate-paths.js'); const pathsCheckCode = timeExecution('Path validation', () => { const pathsCheck = spawnSync('node', [validatePathsPath, '--staged'], { stdio: 'inherit' }); return pathsCheck.status || 0; }); if (pathsCheckCode !== 0) { exitCode = 1; } // Amendment #7: Git is source of truth - no auto-metadata generation // Disabled: update-genie-markdown-metadata.cjs (timestamps/versions duplicate git data) // Run worktree access prevention check (bash script) console.log('🔍 Checking for Forge worktree violations...'); const worktreeCheckPath = path.join(gitRoot, '.genie', 'scripts', 'prevent-worktree-access.sh'); const worktreeCheckCode = timeExecution('Worktree validation', () => { const worktreeCheck = spawnSync('bash', [worktreeCheckPath], { stdio: 'inherit' }); return worktreeCheck.status || 0; }); if (worktreeCheckCode !== 0) { exitCode = 1; } for (const v of validations) { const code = timeExecution(v.replace('.cjs', ''), () => run(v)); if (code !== 0) exitCode = 1; } // Amendment #7: Removed generate-workspace-summary.cjs (redundant with hand-curated knowledge graph in AGENTS.md) // Removed migrate-qa-from-bugs.cjs (generated useless TBD files in wrong location, scenarios-from-bugs.md is sufficient) // Generate token usage and quality summary (non-blocking) try { timeExecution('Token counting', () => { spawnSync('node', [path.join(gitRoot, '.genie', 'scripts', 'token-efficiency', 'count-tokens.cjs')], { stdio: 'inherit' }); return 0; }); } catch (e) { console.warn('⚠️ Token usage script failed (non-blocking)'); } try { timeExecution('Quality gate check', () => { spawnSync('node', [path.join(gitRoot, '.genie', 'scripts', 'token-efficiency', 'quality-gate.cjs')], { stdio: 'inherit' }); return 0; }); } catch (e) { console.warn('⚠️ Token quality gate error (non-blocking)'); } // TODO: Re-enable Genie background advisory when `genie run` CLI is stable // Background advisory currently disabled for performance and reliability // Future: Async learning/analysis of commit patterns, wish alignment, etc. // const workflow = runGenie('neurons/git/commit-advisory', 'Pre-commit validation'); // if (workflow.message) console.log(`- Note: ${workflow.message}`); // Commit message suggestion (non-blocking, advisory only) // Generates conventional commit message from staged diff // Disabled for now - enable when genie run is stable in hooks // try { // const suggestion = execSync('genie run commit-suggester --raw --quiet', { encoding: 'utf8', cwd: gitRoot }).trim(); // if (suggestion) { // const suggestedMsgPath = path.join(gitRoot, '.git', 'SUGGESTED_COMMIT'); // require('fs').writeFileSync(suggestedMsgPath, suggestion); // console.log('💡 Commit message suggestion saved to .git/SUGGESTED_COMMIT'); // console.log(' Use: git commit -F .git/SUGGESTED_COMMIT'); // } // } catch (e) { // // Silently skip if genie run fails (non-blocking) // } // Token-efficient summary const totalDuration = Date.now() - totalStart; if (exitCode === 0) { console.log('- Result: ✅ Pre-commit validations passed'); console.log('- Reinforcer: Save tokens — keep outputs concise'); } else { console.log('- Result: ❌ Some validations failed'); console.log('- Next: Fix issues above and retry commit'); console.log('- Reinforcer: Commit small and often; attach evidence paths when relevant'); } console.log(`⏱️ Total pre-commit time: ${totalDuration}ms`); process.exit(exitCode); } main();