mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 18:01:22 +00:00
feat(coding-agent): support glob patterns in pi manifest arrays
- Manifest extensions/skills/prompts/themes arrays now support glob patterns - Use !pattern for exclusions (e.g., '!**/deprecated/*') - Enables packages to bundle dependencies and selectively include resources
This commit is contained in:
parent
6beeafed17
commit
ed75a8320b
2 changed files with 112 additions and 12 deletions
|
|
@ -797,10 +797,10 @@ export class DefaultPackageManager implements PackageManager {
|
|||
// No filter: load everything based on manifest or directory structure
|
||||
const manifest = this.readPiManifest(packageRoot);
|
||||
if (manifest) {
|
||||
this.addManifestEntries(manifest.extensions, packageRoot, accumulator.extensions);
|
||||
this.addManifestEntries(manifest.skills, packageRoot, accumulator.skills);
|
||||
this.addManifestEntries(manifest.prompts, packageRoot, accumulator.prompts);
|
||||
this.addManifestEntries(manifest.themes, packageRoot, accumulator.themes);
|
||||
this.addManifestEntries(manifest.extensions, packageRoot, "extensions", accumulator.extensions);
|
||||
this.addManifestEntries(manifest.skills, packageRoot, "skills", accumulator.skills);
|
||||
this.addManifestEntries(manifest.prompts, packageRoot, "prompts", accumulator.prompts);
|
||||
this.addManifestEntries(manifest.themes, packageRoot, "themes", accumulator.themes);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -833,7 +833,7 @@ export class DefaultPackageManager implements PackageManager {
|
|||
private collectDefaultExtensions(packageRoot: string, accumulator: ResourceAccumulator): void {
|
||||
const manifest = this.readPiManifest(packageRoot);
|
||||
if (manifest?.extensions) {
|
||||
this.addManifestEntries(manifest.extensions, packageRoot, accumulator.extensions);
|
||||
this.addManifestEntries(manifest.extensions, packageRoot, "extensions", accumulator.extensions);
|
||||
return;
|
||||
}
|
||||
const extensionsDir = join(packageRoot, "extensions");
|
||||
|
|
@ -845,7 +845,7 @@ export class DefaultPackageManager implements PackageManager {
|
|||
private collectDefaultSkills(packageRoot: string, accumulator: ResourceAccumulator): void {
|
||||
const manifest = this.readPiManifest(packageRoot);
|
||||
if (manifest?.skills) {
|
||||
this.addManifestEntries(manifest.skills, packageRoot, accumulator.skills);
|
||||
this.addManifestEntries(manifest.skills, packageRoot, "skills", accumulator.skills);
|
||||
return;
|
||||
}
|
||||
const skillsDir = join(packageRoot, "skills");
|
||||
|
|
@ -857,7 +857,7 @@ export class DefaultPackageManager implements PackageManager {
|
|||
private collectDefaultPrompts(packageRoot: string, accumulator: ResourceAccumulator): void {
|
||||
const manifest = this.readPiManifest(packageRoot);
|
||||
if (manifest?.prompts) {
|
||||
this.addManifestEntries(manifest.prompts, packageRoot, accumulator.prompts);
|
||||
this.addManifestEntries(manifest.prompts, packageRoot, "prompts", accumulator.prompts);
|
||||
return;
|
||||
}
|
||||
const promptsDir = join(packageRoot, "prompts");
|
||||
|
|
@ -869,7 +869,7 @@ export class DefaultPackageManager implements PackageManager {
|
|||
private collectDefaultThemes(packageRoot: string, accumulator: ResourceAccumulator): void {
|
||||
const manifest = this.readPiManifest(packageRoot);
|
||||
if (manifest?.themes) {
|
||||
this.addManifestEntries(manifest.themes, packageRoot, accumulator.themes);
|
||||
this.addManifestEntries(manifest.themes, packageRoot, "themes", accumulator.themes);
|
||||
return;
|
||||
}
|
||||
const themesDir = join(packageRoot, "themes");
|
||||
|
|
@ -972,12 +972,60 @@ export class DefaultPackageManager implements PackageManager {
|
|||
}
|
||||
}
|
||||
|
||||
private addManifestEntries(entries: string[] | undefined, root: string, target: Set<string>): void {
|
||||
private addManifestEntries(
|
||||
entries: string[] | undefined,
|
||||
root: string,
|
||||
resourceType: ResourceType,
|
||||
target: Set<string>,
|
||||
): void {
|
||||
if (!entries) return;
|
||||
for (const entry of entries) {
|
||||
const resolved = resolve(root, entry);
|
||||
this.addPath(target, resolved);
|
||||
|
||||
if (!hasPatterns(entries)) {
|
||||
// No patterns - resolve directly
|
||||
for (const entry of entries) {
|
||||
const resolved = resolve(root, entry);
|
||||
this.addPath(target, resolved);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Has patterns - enumerate and filter
|
||||
const allFiles = this.collectAllManifestFiles(entries, root, resourceType);
|
||||
const patterns = entries.filter(isPattern);
|
||||
const filtered = applyPatterns(allFiles, patterns, root);
|
||||
for (const f of filtered) {
|
||||
this.addPath(target, f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect files from manifest entries for pattern matching.
|
||||
* Plain paths are resolved and enumerated; pattern strings are skipped (used for filtering).
|
||||
*/
|
||||
private collectAllManifestFiles(entries: string[], root: string, resourceType: ResourceType): string[] {
|
||||
const files: string[] = [];
|
||||
for (const entry of entries) {
|
||||
if (isPattern(entry)) continue;
|
||||
|
||||
const resolved = resolve(root, entry);
|
||||
if (!existsSync(resolved)) continue;
|
||||
|
||||
try {
|
||||
const stats = statSync(resolved);
|
||||
if (stats.isFile()) {
|
||||
files.push(resolved);
|
||||
} else if (stats.isDirectory()) {
|
||||
if (resourceType === "skills") {
|
||||
files.push(...collectSkillEntries(resolved));
|
||||
} else {
|
||||
files.push(...collectFiles(resolved, FILE_PATTERNS[resourceType]));
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private addPath(set: Set<string>, value: string): void {
|
||||
|
|
|
|||
|
|
@ -232,6 +232,58 @@ Content`,
|
|||
});
|
||||
});
|
||||
|
||||
describe("pattern filtering in pi manifest", () => {
|
||||
it("should support glob patterns in manifest extensions", async () => {
|
||||
const pkgDir = join(tempDir, "manifest-pkg");
|
||||
mkdirSync(join(pkgDir, "extensions"), { recursive: true });
|
||||
mkdirSync(join(pkgDir, "node_modules/dep/extensions"), { recursive: true });
|
||||
writeFileSync(join(pkgDir, "extensions", "local.ts"), "export default function() {}");
|
||||
writeFileSync(join(pkgDir, "node_modules/dep/extensions", "remote.ts"), "export default function() {}");
|
||||
writeFileSync(join(pkgDir, "node_modules/dep/extensions", "skip.ts"), "export default function() {}");
|
||||
writeFileSync(
|
||||
join(pkgDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "manifest-pkg",
|
||||
pi: {
|
||||
extensions: ["extensions", "node_modules/dep/extensions", "!**/skip.ts"],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await packageManager.resolveExtensionSources([pkgDir]);
|
||||
expect(result.extensions.some((p) => p.endsWith("local.ts"))).toBe(true);
|
||||
expect(result.extensions.some((p) => p.endsWith("remote.ts"))).toBe(true);
|
||||
expect(result.extensions.some((p) => p.endsWith("skip.ts"))).toBe(false);
|
||||
});
|
||||
|
||||
it("should support glob patterns in manifest skills", async () => {
|
||||
const pkgDir = join(tempDir, "skill-manifest-pkg");
|
||||
mkdirSync(join(pkgDir, "skills/good-skill"), { recursive: true });
|
||||
mkdirSync(join(pkgDir, "skills/bad-skill"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(pkgDir, "skills/good-skill", "SKILL.md"),
|
||||
"---\nname: good-skill\ndescription: Good\n---\nContent",
|
||||
);
|
||||
writeFileSync(
|
||||
join(pkgDir, "skills/bad-skill", "SKILL.md"),
|
||||
"---\nname: bad-skill\ndescription: Bad\n---\nContent",
|
||||
);
|
||||
writeFileSync(
|
||||
join(pkgDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "skill-manifest-pkg",
|
||||
pi: {
|
||||
skills: ["skills", "!**/bad-skill"],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await packageManager.resolveExtensionSources([pkgDir]);
|
||||
expect(result.skills.some((p) => p.includes("good-skill"))).toBe(true);
|
||||
expect(result.skills.some((p) => p.includes("bad-skill"))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pattern filtering in package filters", () => {
|
||||
it("should exclude extensions from package with ! pattern", async () => {
|
||||
const pkgDir = join(tempDir, "pattern-pkg");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue