diff --git a/src/agents/sessions/package-manager.test.ts b/src/agents/sessions/package-manager.test.ts index 68b4f70c00f2..39c088386932 100644 --- a/src/agents/sessions/package-manager.test.ts +++ b/src/agents/sessions/package-manager.test.ts @@ -133,14 +133,32 @@ describe("DefaultPackageManager", () => { const insidePrompt = join(configRoot, "prompts", "inside.md"); const insideTheme = join(configRoot, "themes", "inside.json"); const insideExtension = join(configRoot, "extensions", "inside.ts"); + const ignoredPrompt = join(configRoot, "prompts", "ignored.md"); + const ignoredTheme = join(configRoot, "themes", "ignored.json"); + const hiddenPrompt = join(configRoot, "prompts", ".hidden.md"); + const hiddenTheme = join(configRoot, "themes", ".hidden.json"); + const nestedPrompt = join(configRoot, "prompts", "nested", "nested.md"); + const nestedTheme = join(configRoot, "themes", "nested", "nested.json"); + const wrongPromptType = join(configRoot, "prompts", "wrong.json"); + const wrongThemeType = join(configRoot, "themes", "wrong.md"); await mkdir(join(root, ".git")); - await mkdir(join(configRoot, "prompts"), { recursive: true }); - await mkdir(join(configRoot, "themes"), { recursive: true }); + await mkdir(join(configRoot, "prompts", "nested"), { recursive: true }); + await mkdir(join(configRoot, "themes", "nested"), { recursive: true }); await mkdir(join(configRoot, "extensions"), { recursive: true }); await mkdir(outsideRoot, { recursive: true }); await writeFile(insidePrompt, "# Inside\n", "utf-8"); await writeFile(insideTheme, "{}\n", "utf-8"); await writeFile(insideExtension, "export default {};\n", "utf-8"); + await writeFile(ignoredPrompt, "# Ignored\n", "utf-8"); + await writeFile(ignoredTheme, "{}\n", "utf-8"); + await writeFile(nestedPrompt, "# Nested\n", "utf-8"); + await writeFile(nestedTheme, "{}\n", "utf-8"); + await writeFile(hiddenPrompt, "# Hidden\n", "utf-8"); + await writeFile(hiddenTheme, "{}\n", "utf-8"); + await writeFile(wrongPromptType, "{}\n", "utf-8"); + await writeFile(wrongThemeType, "# Wrong\n", "utf-8"); + await writeFile(join(configRoot, "prompts", ".ignore"), "ignored.md\n", "utf-8"); + await writeFile(join(configRoot, "themes", ".ignore"), "ignored.json\n", "utf-8"); await writeFile(join(outsideRoot, "outside.md"), "# Outside\n", "utf-8"); await writeFile(join(outsideRoot, "outside.json"), "{}\n", "utf-8"); await writeFile(join(outsideRoot, "outside.ts"), "export default {};\n", "utf-8"); @@ -161,12 +179,22 @@ describe("DefaultPackageManager", () => { }); const resolved = await manager.resolve(); + const promptPaths = resolved.prompts.map((prompt) => prompt.path); + const themePaths = resolved.themes.map((theme) => theme.path); - expect(resolved.prompts.map((prompt) => prompt.path)).toContain(insidePrompt); - expect(resolved.themes.map((theme) => theme.path)).toContain(insideTheme); + expect(promptPaths).toContain(insidePrompt); + expect(themePaths).toContain(insideTheme); + expect(promptPaths).not.toContain(ignoredPrompt); + expect(themePaths).not.toContain(ignoredTheme); + expect(promptPaths).not.toContain(nestedPrompt); + expect(themePaths).not.toContain(nestedTheme); + expect(promptPaths).not.toContain(hiddenPrompt); + expect(themePaths).not.toContain(hiddenTheme); + expect(promptPaths).not.toContain(wrongPromptType); + expect(themePaths).not.toContain(wrongThemeType); expect(resolved.extensions.map((extension) => extension.path)).toContain(insideExtension); - expect(resolved.prompts.some((prompt) => prompt.path.includes("linked"))).toBe(false); - expect(resolved.themes.some((theme) => theme.path.includes("linked"))).toBe(false); + expect(promptPaths.some((promptPath) => promptPath.includes("linked"))).toBe(false); + expect(themePaths.some((themePath) => themePath.includes("linked"))).toBe(false); expect(resolved.extensions.some((extension) => extension.path.includes("linked"))).toBe(false); }); diff --git a/src/agents/sessions/package-manager.ts b/src/agents/sessions/package-manager.ts index 814af2099c78..5c867d84dc46 100644 --- a/src/agents/sessions/package-manager.ts +++ b/src/agents/sessions/package-manager.ts @@ -110,6 +110,7 @@ interface PackageFilter { } type ResourceType = "extensions" | "skills" | "prompts" | "themes"; +type TopLevelAutoResourceType = Extract; const RESOURCE_TYPES: ResourceType[] = ["extensions", "skills", "prompts", "themes"]; @@ -351,7 +352,10 @@ function collectAncestorAgentsSkillDirs(startDir: string): string[] { return skillDirs; } -function collectAutoPromptEntries(dir: string): string[] { +function collectTopLevelAutoResourceEntries( + dir: string, + resourceType: TopLevelAutoResourceType, +): string[] { const entries: string[] = []; if (!existsSync(dir)) { return entries; @@ -388,55 +392,7 @@ function collectAutoPromptEntries(dir: string): string[] { continue; } - if (isFile && entry.name.endsWith(".md")) { - entries.push(fullPath); - } - } - } catch { - // Ignore errors - } - - return entries; -} - -function collectAutoThemeEntries(dir: string): string[] { - const entries: string[] = []; - if (!existsSync(dir)) { - return entries; - } - - const ig = ignore(); - addIgnoreRules(ig, dir, dir); - - try { - const dirEntries = readdirSync(dir, { withFileTypes: true }); - for (const entry of dirEntries) { - if (entry.name.startsWith(".")) { - continue; - } - if (entry.name === "node_modules") { - continue; - } - - const fullPath = join(dir, entry.name); - if (!isRealPathWithinRoot(dir, fullPath)) { - continue; - } - let isFile = entry.isFile(); - if (entry.isSymbolicLink()) { - try { - isFile = statSync(fullPath).isFile(); - } catch { - continue; - } - } - - const relPath = toPosixPath(relative(dir, fullPath)); - if (ig.ignores(relPath)) { - continue; - } - - if (isFile && entry.name.endsWith(".json")) { + if (isFile && FILE_PATTERNS[resourceType].test(entry.name)) { entries.push(fullPath); } } @@ -1415,14 +1371,14 @@ export class DefaultPackageManager implements PackageManager { addResources( "prompts", - collectAutoPromptEntries(projectDirs.prompts), + collectTopLevelAutoResourceEntries(projectDirs.prompts, "prompts"), projectMetadata, projectOverrides.prompts, projectBaseDir, ); addResources( "themes", - collectAutoThemeEntries(projectDirs.themes), + collectTopLevelAutoResourceEntries(projectDirs.themes, "themes"), projectMetadata, projectOverrides.themes, projectBaseDir, @@ -1462,14 +1418,14 @@ export class DefaultPackageManager implements PackageManager { addResources( "prompts", - collectAutoPromptEntries(userDirs.prompts), + collectTopLevelAutoResourceEntries(userDirs.prompts, "prompts"), userMetadata, userOverrides.prompts, globalBaseDir, ); addResources( "themes", - collectAutoThemeEntries(userDirs.themes), + collectTopLevelAutoResourceEntries(userDirs.themes, "themes"), userMetadata, userOverrides.themes, globalBaseDir,