This commit is contained in:
Colby McHenry
2026-01-18 16:25:00 -06:00
parent 08ccabb5a9
commit cc6e7a5c89
57 changed files with 23315 additions and 1 deletions
+284
View File
@@ -0,0 +1,284 @@
/**
* Git Hooks Management
*
* Installs and manages git hooks for automatic incremental indexing.
*/
import * as fs from 'fs';
import * as path from 'path';
/**
* Name of the post-commit hook file
*/
const POST_COMMIT_HOOK = 'post-commit';
/**
* Marker comment to identify CodeGraph-managed hooks
*/
const CODEGRAPH_MARKER = '# CodeGraph auto-sync hook';
/**
* The post-commit hook script content
*
* This script:
* 1. Checks if codegraph CLI is available
* 2. Falls back to npx if not
* 3. Runs sync in the background to avoid blocking commits
*/
const POST_COMMIT_SCRIPT = `#!/bin/sh
${CODEGRAPH_MARKER}
# This hook was installed by CodeGraph to enable automatic incremental indexing.
# It runs after each commit to keep the code graph in sync.
# To remove this hook, run: codegraph hooks --remove
# Or delete this file manually.
# Run sync in background to avoid blocking the commit
(
# Check if we're in a CodeGraph project
if [ ! -d ".codegraph" ]; then
exit 0
fi
# Try to run codegraph sync
if command -v codegraph >/dev/null 2>&1; then
codegraph sync --quiet 2>/dev/null &
elif command -v npx >/dev/null 2>&1; then
npx codegraph sync --quiet 2>/dev/null &
fi
) &
exit 0
`;
/**
* Result of hook installation
*/
export interface HookInstallResult {
success: boolean;
hookPath: string;
message: string;
previousHookBackedUp?: boolean;
backupPath?: string;
}
/**
* Result of hook removal
*/
export interface HookRemoveResult {
success: boolean;
message: string;
restoredFromBackup?: boolean;
}
/**
* Git hooks manager
*/
export class GitHooksManager {
private gitDir: string;
private hooksDir: string;
constructor(projectRoot: string) {
this.gitDir = path.join(projectRoot, '.git');
this.hooksDir = path.join(this.gitDir, 'hooks');
}
/**
* Check if the project is a git repository
*/
isGitRepository(): boolean {
return fs.existsSync(this.gitDir) && fs.statSync(this.gitDir).isDirectory();
}
/**
* Check if the post-commit hook is installed by CodeGraph
*/
isHookInstalled(): boolean {
const hookPath = path.join(this.hooksDir, POST_COMMIT_HOOK);
if (!fs.existsSync(hookPath)) {
return false;
}
try {
const content = fs.readFileSync(hookPath, 'utf-8');
return content.includes(CODEGRAPH_MARKER);
} catch {
return false;
}
}
/**
* Install the post-commit hook
*
* If a hook already exists:
* - If it's a CodeGraph hook, update it
* - If it's a user hook, back it up and install ours
*/
installHook(): HookInstallResult {
const hookPath = path.join(this.hooksDir, POST_COMMIT_HOOK);
// Check if this is a git repository
if (!this.isGitRepository()) {
return {
success: false,
hookPath,
message: 'Not a git repository. Initialize git first with: git init',
};
}
// Ensure hooks directory exists
if (!fs.existsSync(this.hooksDir)) {
try {
fs.mkdirSync(this.hooksDir, { recursive: true });
} catch (error) {
return {
success: false,
hookPath,
message: `Failed to create hooks directory: ${error}`,
};
}
}
// Check for existing hook
let previousHookBackedUp = false;
let backupPath: string | undefined;
if (fs.existsSync(hookPath)) {
try {
const existingContent = fs.readFileSync(hookPath, 'utf-8');
// If it's already our hook, just update it
if (existingContent.includes(CODEGRAPH_MARKER)) {
fs.writeFileSync(hookPath, POST_COMMIT_SCRIPT, { mode: 0o755 });
return {
success: true,
hookPath,
message: 'Post-commit hook updated.',
};
}
// It's a user hook - back it up
backupPath = `${hookPath}.codegraph-backup`;
fs.copyFileSync(hookPath, backupPath);
previousHookBackedUp = true;
} catch (error) {
return {
success: false,
hookPath,
message: `Failed to backup existing hook: ${error}`,
};
}
}
// Write the hook
try {
fs.writeFileSync(hookPath, POST_COMMIT_SCRIPT, { mode: 0o755 });
} catch (error) {
return {
success: false,
hookPath,
message: `Failed to write hook: ${error}`,
};
}
const message = previousHookBackedUp
? `Post-commit hook installed. Previous hook backed up to: ${backupPath}`
: 'Post-commit hook installed.';
return {
success: true,
hookPath,
message,
previousHookBackedUp,
backupPath,
};
}
/**
* Remove the CodeGraph post-commit hook
*
* If a backup exists, restore it.
*/
removeHook(): HookRemoveResult {
const hookPath = path.join(this.hooksDir, POST_COMMIT_HOOK);
const backupPath = `${hookPath}.codegraph-backup`;
// Check if hook exists
if (!fs.existsSync(hookPath)) {
return {
success: true,
message: 'No post-commit hook found.',
};
}
// Check if it's our hook
try {
const content = fs.readFileSync(hookPath, 'utf-8');
if (!content.includes(CODEGRAPH_MARKER)) {
return {
success: false,
message: 'Post-commit hook was not installed by CodeGraph. Not removing.',
};
}
} catch (error) {
return {
success: false,
message: `Failed to read hook: ${error}`,
};
}
// Remove the hook
try {
fs.unlinkSync(hookPath);
} catch (error) {
return {
success: false,
message: `Failed to remove hook: ${error}`,
};
}
// Restore backup if it exists
if (fs.existsSync(backupPath)) {
try {
fs.renameSync(backupPath, hookPath);
return {
success: true,
message: 'Post-commit hook removed. Previous hook restored from backup.',
restoredFromBackup: true,
};
} catch (error) {
return {
success: true,
message: `Post-commit hook removed. Warning: failed to restore backup: ${error}`,
restoredFromBackup: false,
};
}
}
return {
success: true,
message: 'Post-commit hook removed.',
};
}
/**
* Get the path to the hooks directory
*/
getHooksDir(): string {
return this.hooksDir;
}
/**
* Get the path to the post-commit hook
*/
getHookPath(): string {
return path.join(this.hooksDir, POST_COMMIT_HOOK);
}
}
/**
* Create a git hooks manager for a project
*/
export function createGitHooksManager(projectRoot: string): GitHooksManager {
return new GitHooksManager(projectRoot);
}
+18
View File
@@ -0,0 +1,18 @@
/**
* Sync Module
*
* Provides synchronization functionality for keeping the code graph
* up-to-date with file system changes.
*
* Components:
* - Git hooks for automatic post-commit syncing
* - Content hashing for change detection (in extraction module)
* - Incremental reindexing (in extraction module)
*/
export {
GitHooksManager,
createGitHooksManager,
HookInstallResult,
HookRemoveResult,
} from './git-hooks';