refactor(agents): share top-level resource collection (#104988)

This commit is contained in:
Vincent Koc
2026-07-12 13:05:34 +08:00
committed by GitHub
parent f3971bbd56
commit 2ff8fcab07
2 changed files with 44 additions and 60 deletions

View File

@@ -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);
});

View File

@@ -110,6 +110,7 @@ interface PackageFilter {
}
type ResourceType = "extensions" | "skills" | "prompts" | "themes";
type TopLevelAutoResourceType = Extract<ResourceType, "prompts" | "themes">;
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,