From 89d394ed3e5c8cd1a8dcade96f97bacc5ea0331b Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 18 Apr 2026 14:13:12 +0900 Subject: [PATCH] fix(claude-code-command-loader): skip EXCLUDED_DIRS and memoize per directory Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../loader-cache.ts | 37 +++++++++++++++++++ .../claude-code-command-loader/loader.ts | 30 ++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/features/claude-code-command-loader/loader-cache.ts diff --git a/src/features/claude-code-command-loader/loader-cache.ts b/src/features/claude-code-command-loader/loader-cache.ts new file mode 100644 index 000000000..9f0d4d195 --- /dev/null +++ b/src/features/claude-code-command-loader/loader-cache.ts @@ -0,0 +1,37 @@ +import { promises as fs } from "fs" +import { resolve } from "path" + +import type { CommandDefinition } from "./types" + +const commandLoaderCache = new Map>>() + +export async function getCommandLoaderCacheKey(directory?: string): Promise { + const resolvedDirectory = resolve(directory ?? process.cwd()) + + try { + return await fs.realpath(resolvedDirectory) + } catch { + return resolvedDirectory + } +} + +export function getCachedCommands( + cacheKey: string, +): Promise> | undefined { + return commandLoaderCache.get(cacheKey) +} + +export function setCachedCommands( + cacheKey: string, + commands: Promise>, +): void { + commandLoaderCache.set(cacheKey, commands) +} + +export function deleteCachedCommands(cacheKey: string): void { + commandLoaderCache.delete(cacheKey) +} + +export function clearCommandLoaderCache(): void { + commandLoaderCache.clear() +} diff --git a/src/features/claude-code-command-loader/loader.ts b/src/features/claude-code-command-loader/loader.ts index b052f56bd..6ee178b66 100644 --- a/src/features/claude-code-command-loader/loader.ts +++ b/src/features/claude-code-command-loader/loader.ts @@ -4,13 +4,23 @@ import { parseFrontmatter } from "../../shared/frontmatter" import { sanitizeModelField } from "../../shared/model-sanitizer" import { isMarkdownFile } from "../../shared/file-utils" import { + EXCLUDED_DIRS, findProjectOpencodeCommandDirs, getClaudeConfigDir, getOpenCodeCommandDirs, } from "../../shared" import { log } from "../../shared/logger" +import { + clearCommandLoaderCache, + deleteCachedCommands, + getCachedCommands, + getCommandLoaderCacheKey, + setCachedCommands, +} from "./loader-cache" import type { CommandScope, CommandDefinition, CommandFrontmatter, LoadedCommand } from "./types" +export { clearCommandLoaderCache } + async function loadCommandsFromDir( commandsDir: string, scope: CommandScope, @@ -48,6 +58,7 @@ async function loadCommandsFromDir( for (const entry of entries) { if (entry.isDirectory()) { + if (EXCLUDED_DIRS.has(entry.name)) continue if (entry.name.startsWith(".")) continue const subDirPath = join(commandsDir, entry.name) const subPrefix = prefix ? `${prefix}/${entry.name}` : entry.name @@ -159,11 +170,26 @@ export async function loadOpencodeProjectCommands(directory?: string): Promise> { - const [user, project, global, projectOpencode] = await Promise.all([ + const cacheKey = await getCommandLoaderCacheKey(directory) + const cachedCommands = getCachedCommands(cacheKey) + if (cachedCommands) { + return cachedCommands + } + + const loadCommandsPromise = Promise.all([ loadUserCommands(), loadProjectCommands(directory), loadOpencodeGlobalCommands(), loadOpencodeProjectCommands(directory), ]) - return { ...projectOpencode, ...global, ...project, ...user } + .then(([user, project, global, projectOpencode]) => { + return { ...projectOpencode, ...global, ...project, ...user } + }) + .catch((error) => { + deleteCachedCommands(cacheKey) + throw error + }) + + setCachedCommands(cacheKey, loadCommandsPromise) + return loadCommandsPromise }