fix: use settings as source of truth for hooks config state (#633)

This commit is contained in:
jnjpng
2026-01-22 12:09:42 -08:00
committed by GitHub
parent b9ac39e74d
commit dccc18ee46
5 changed files with 102 additions and 249 deletions

View File

@@ -1,116 +1,71 @@
// src/hooks/loader.ts
// Loads and matches hooks from settings
// Loads and matches hooks from settings-manager
import { homedir } from "node:os";
import { join } from "node:path";
import { exists, readFile } from "../utils/fs.js";
import { settingsManager } from "../settings-manager";
import type { HookCommand, HookEvent, HooksConfig } from "./types";
/**
* Cache for loaded hooks configurations
*/
let globalHooksCache: HooksConfig | null = null;
const projectHooksCache: Map<string, HooksConfig> = new Map();
const projectLocalHooksCache: Map<string, HooksConfig> = new Map();
/**
* Clear hooks cache (useful for testing or when settings change)
* Clear hooks cache - kept for API compatibility with existing callers.
*/
export function clearHooksCache(): void {
globalHooksCache = null;
projectHooksCache.clear();
projectLocalHooksCache.clear();
}
/**
* Get the path to global hooks settings
*/
function getGlobalSettingsPath(): string {
const home = process.env.HOME || homedir();
return join(home, ".letta", "settings.json");
}
/**
* Get the path to project hooks settings
*/
function getProjectSettingsPath(workingDirectory: string): string {
return join(workingDirectory, ".letta", "settings.json");
}
/**
* Get the path to project-local hooks settings (gitignored)
*/
function getProjectLocalSettingsPath(workingDirectory: string): string {
return join(workingDirectory, ".letta", "settings.local.json");
}
/**
* Load hooks configuration from a settings file
*/
async function loadHooksFromFile(path: string): Promise<HooksConfig | null> {
if (!exists(path)) {
return null;
}
try {
const content = await readFile(path);
const settings = JSON.parse(content) as { hooks?: HooksConfig };
return settings.hooks || null;
} catch (error) {
// Silently ignore parse errors - don't break the app for bad hooks config
console.warn(`Failed to load hooks from ${path}:`, error);
return null;
}
// Settings-manager handles caching
}
/**
* Load global hooks configuration from ~/.letta/settings.json
* Uses settings-manager cache (loaded at app startup)
*/
export async function loadGlobalHooks(): Promise<HooksConfig> {
if (globalHooksCache !== null) {
return globalHooksCache;
export function loadGlobalHooks(): HooksConfig {
try {
return settingsManager.getSettings().hooks || {};
} catch {
// Settings not initialized yet
return {};
}
const path = getGlobalSettingsPath();
const hooks = await loadHooksFromFile(path);
globalHooksCache = hooks || {};
return globalHooksCache;
}
/**
* Load project hooks configuration from .letta/settings.json
* Uses settings-manager cache
*/
export async function loadProjectHooks(
workingDirectory: string = process.cwd(),
): Promise<HooksConfig> {
const cached = projectHooksCache.get(workingDirectory);
if (cached !== undefined) {
return cached;
try {
// Ensure project settings are loaded
try {
settingsManager.getProjectSettings(workingDirectory);
} catch {
await settingsManager.loadProjectSettings(workingDirectory);
}
return settingsManager.getProjectSettings(workingDirectory)?.hooks || {};
} catch {
// Settings not available
return {};
}
const path = getProjectSettingsPath(workingDirectory);
const hooks = await loadHooksFromFile(path);
const result = hooks || {};
projectHooksCache.set(workingDirectory, result);
return result;
}
/**
* Load project-local hooks configuration from .letta/settings.local.json
* Uses settings-manager cache
*/
export async function loadProjectLocalHooks(
workingDirectory: string = process.cwd(),
): Promise<HooksConfig> {
const cached = projectLocalHooksCache.get(workingDirectory);
if (cached !== undefined) {
return cached;
try {
// Ensure local project settings are loaded
try {
settingsManager.getLocalProjectSettings(workingDirectory);
} catch {
await settingsManager.loadLocalProjectSettings(workingDirectory);
}
return (
settingsManager.getLocalProjectSettings(workingDirectory)?.hooks || {}
);
} catch {
// Settings not available
return {};
}
const path = getProjectLocalSettingsPath(workingDirectory);
const hooks = await loadHooksFromFile(path);
const result = hooks || {};
projectLocalHooksCache.set(workingDirectory, result);
return result;
}
/**
@@ -152,7 +107,7 @@ export async function loadHooks(
workingDirectory: string = process.cwd(),
): Promise<HooksConfig> {
const [global, project, projectLocal] = await Promise.all([
loadGlobalHooks(),
Promise.resolve(loadGlobalHooks()),
loadProjectHooks(workingDirectory),
loadProjectLocalHooks(workingDirectory),
]);