diff --git a/src/commands/__tests__/hook.test.ts b/src/commands/__tests__/hook.test.ts index d2abdd9..0b4e2c7 100644 --- a/src/commands/__tests__/hook.test.ts +++ b/src/commands/__tests__/hook.test.ts @@ -42,7 +42,7 @@ afterEach(async () => { }); function hookPath(): string { - return path.join(tmpDir, '.git', 'hooks', 'pre-commit'); + return path.join(tmpDir, '.git', 'hooks', 'pre-push'); } describe('hook install', () => { diff --git a/src/commands/hook/index.ts b/src/commands/hook/index.ts index 5d2dc3f..50856a1 100644 --- a/src/commands/hook/index.ts +++ b/src/commands/hook/index.ts @@ -4,12 +4,12 @@ import { uninstallAction } from './uninstall.js'; import { statusAction } from './status.js'; export const hookCommand = new Command('hook') - .description('Manage pre-commit hook for automatic code review'); + .description('Manage pre-push hook for automatic code review'); hookCommand .command('install') - .description('Install pre-commit hook for automatic code review') - .option('--fail-on ', 'Minimum severity to block commit (info, warning, error, critical)', 'critical') + .description('Install pre-push hook for automatic code review') + .option('--fail-on ', 'Minimum severity to block push (info, warning, error, critical)', 'critical') .option('--fast', 'Use fast mode for review (default: true)', true) .option('--no-fast', 'Disable fast mode for review') .option('--force', 'Overwrite existing hook without prompting') @@ -17,10 +17,10 @@ hookCommand hookCommand .command('uninstall') - .description('Remove pre-commit hook installed by kodus') + .description('Remove pre-push hook installed by kodus') .action(uninstallAction); hookCommand .command('status') - .description('Show pre-commit hook status') + .description('Show pre-push hook status') .action(statusAction); diff --git a/src/commands/hook/install.ts b/src/commands/hook/install.ts index ff3969f..882cf27 100644 --- a/src/commands/hook/install.ts +++ b/src/commands/hook/install.ts @@ -7,12 +7,14 @@ import { gitService } from '../../services/git.service.js'; const KODUS_MARKER = '# kodus-hook'; function generateHookScript(failOn: string, fast: boolean): string { - const flags = ['--staged']; + const flags: string[] = []; if (fast) flags.push('--fast'); flags.push('--fail-on', failOn); flags.push('--format', 'terminal'); flags.push('--quiet'); + const reviewFlags = flags.join(' '); + return `#!/bin/sh ${KODUS_MARKER} — installed by kodus CLI # To uninstall: kodus hook uninstall @@ -24,13 +26,39 @@ fi # Check if kodus is available if ! command -v kodus >/dev/null 2>&1; then - echo "Warning: kodus CLI not found. Skipping pre-commit review." + echo "Warning: kodus CLI not found. Skipping pre-push review." echo "Install: npm install -g @kodus/cli" exit 0 fi -# Run review on staged files -kodus review ${flags.join(' ')} +remote="$1" +current_branch="$(git symbolic-ref --short HEAD 2>/dev/null)" + +while read local_ref local_sha remote_ref remote_sha; do + # Skip branch deletions + if [ "$local_sha" = "0000000000000000000000000000000000000000" ]; then + continue + fi + + # New branch — no remote state to compare, skip review + if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then + continue + fi + + # Extract branch name from ref (refs/heads/my-branch → my-branch) + branch_name="\${local_ref#refs/heads/}" + + # Only review if pushing the currently checked-out branch + # (--branch compares against HEAD, so reviewing other refs would produce wrong diffs) + if [ "\$branch_name" != "\$current_branch" ]; then + continue + fi + + # Review changes not yet on the remote + if ! kodus review --branch "\${remote}/\${branch_name}" ${reviewFlags}; then + exit 1 + fi +done `; } @@ -50,7 +78,7 @@ export async function installAction(options: { const gitRoot = await gitService.getGitRoot(); const hooksDir = path.join(gitRoot.trim(), '.git', 'hooks'); - const hookPath = path.join(hooksDir, 'pre-commit'); + const hookPath = path.join(hooksDir, 'pre-push'); // Check if hook already exists let existingContent: string | null = null; @@ -68,7 +96,7 @@ export async function installAction(options: { { type: 'confirm', name: 'overwrite', - message: 'A pre-commit hook already exists. Overwrite it?', + message: 'A pre-push hook already exists. Overwrite it?', default: false, }, ]); @@ -87,12 +115,12 @@ export async function installAction(options: { const script = generateHookScript(failOn, fast); await fs.writeFile(hookPath, script, { mode: 0o755 }); - console.log(chalk.green('✓ Pre-commit hook installed successfully!')); + console.log(chalk.green('✓ Pre-push hook installed successfully!')); console.log(chalk.dim(` Path: ${hookPath}`)); console.log(chalk.dim(` Fail on: ${failOn}`)); console.log(chalk.dim(` Fast mode: ${fast ? 'yes' : 'no'}`)); console.log(''); - console.log(chalk.dim('Skip with: KODUS_SKIP_HOOK=1 git commit')); + console.log(chalk.dim('Skip with: KODUS_SKIP_HOOK=1 git push')); console.log(chalk.dim('Remove with: kodus hook uninstall')); } diff --git a/src/commands/hook/status.ts b/src/commands/hook/status.ts index 8141131..13203c1 100644 --- a/src/commands/hook/status.ts +++ b/src/commands/hook/status.ts @@ -12,22 +12,22 @@ export async function statusAction(): Promise { } const gitRoot = await gitService.getGitRoot(); - const hookPath = path.join(gitRoot.trim(), '.git', 'hooks', 'pre-commit'); + const hookPath = path.join(gitRoot.trim(), '.git', 'hooks', 'pre-push'); let content: string; try { content = await fs.readFile(hookPath, 'utf-8'); } catch { - console.log(chalk.yellow('Pre-commit hook: not installed')); + console.log(chalk.yellow('Pre-push hook: not installed')); return; } if (!content.includes(KODUS_MARKER)) { - console.log(chalk.yellow('Pre-commit hook: installed (not by kodus)')); + console.log(chalk.yellow('Pre-push hook: installed (not by kodus)')); return; } - console.log(chalk.green('Pre-commit hook: installed')); + console.log(chalk.green('Pre-push hook: installed')); // Parse config from hook script const failOnMatch = content.match(/--fail-on\s+(\S+)/); diff --git a/src/commands/hook/uninstall.ts b/src/commands/hook/uninstall.ts index aca5236..8652af5 100644 --- a/src/commands/hook/uninstall.ts +++ b/src/commands/hook/uninstall.ts @@ -12,21 +12,21 @@ export async function uninstallAction(): Promise { } const gitRoot = await gitService.getGitRoot(); - const hookPath = path.join(gitRoot.trim(), '.git', 'hooks', 'pre-commit'); + const hookPath = path.join(gitRoot.trim(), '.git', 'hooks', 'pre-push'); let content: string; try { content = await fs.readFile(hookPath, 'utf-8'); } catch { - console.log(chalk.yellow('No pre-commit hook found.')); + console.log(chalk.yellow('No pre-push hook found.')); return; } if (!content.includes(KODUS_MARKER)) { - console.log(chalk.yellow('The pre-commit hook was not installed by kodus. Skipping.')); + console.log(chalk.yellow('The pre-push hook was not installed by kodus. Skipping.')); return; } await fs.unlink(hookPath); - console.log(chalk.green('✓ Pre-commit hook removed successfully.')); + console.log(chalk.green('✓ Pre-push hook removed successfully.')); } diff --git a/tests/integration/cli.integration.test.ts b/tests/integration/cli.integration.test.ts index 53633a9..8d5b801 100644 --- a/tests/integration/cli.integration.test.ts +++ b/tests/integration/cli.integration.test.ts @@ -231,13 +231,13 @@ describe('auth status integration', () => { // Hook commands — install, status, uninstall // --------------------------------------------------------------------------- describe('hook integration', () => { - it('kodus hook install creates pre-commit hook', async () => { + it('kodus hook install creates pre-push hook', async () => { const { stdout, stderr, exitCode } = await runCli(['hook', 'install', '--force']); expect(exitCode).toBe(0); const output = stdout + stderr; expect(output).toContain('installed'); - const hookPath = path.join(gitRepoDir, '.git', 'hooks', 'pre-commit'); + const hookPath = path.join(gitRepoDir, '.git', 'hooks', 'pre-push'); const content = await fs.readFile(hookPath, 'utf-8'); expect(content).toContain('# kodus-hook'); expect(content).toContain('--fail-on critical'); @@ -263,7 +263,7 @@ describe('hook integration', () => { const output = stdout + stderr; expect(output).toContain('removed'); - const hookPath = path.join(gitRepoDir, '.git', 'hooks', 'pre-commit'); + const hookPath = path.join(gitRepoDir, '.git', 'hooks', 'pre-push'); await expect(fs.access(hookPath)).rejects.toThrow(); }); });