mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-21 04:00:10 +00:00
fix(coding-agent): user filters now layer on top of manifest filters
Previously, user filters completely replaced manifest filtering. Now: 1. Manifest patterns are applied first (defines what package provides) 2. User patterns are applied on top (narrows down further) This means if a manifest excludes 10 extensions and user adds one more exclusion, all 11 are excluded (not just the user's one).
This commit is contained in:
parent
f63a353779
commit
375d0bc4d6
2 changed files with 68 additions and 29 deletions
|
|
@ -879,69 +879,70 @@ export class DefaultPackageManager implements PackageManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply filter patterns to a package's resources.
|
* Apply user filter patterns on top of what the manifest provides.
|
||||||
* Supports glob patterns and exclusions (! prefix).
|
* Manifest patterns are applied first, then user patterns narrow down further.
|
||||||
*/
|
*/
|
||||||
private applyPackageFilter(
|
private applyPackageFilter(
|
||||||
packageRoot: string,
|
packageRoot: string,
|
||||||
patterns: string[],
|
userPatterns: string[],
|
||||||
resourceType: ResourceType,
|
resourceType: ResourceType,
|
||||||
target: Set<string>,
|
target: Set<string>,
|
||||||
): void {
|
): void {
|
||||||
if (patterns.length === 0) {
|
if (userPatterns.length === 0) {
|
||||||
|
// Empty array = load none
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasPatterns(patterns)) {
|
// First get what manifest provides (with manifest patterns already applied)
|
||||||
// No patterns - just resolve paths directly
|
const manifestFiles = this.collectManifestFilteredFiles(packageRoot, resourceType);
|
||||||
for (const entry of patterns) {
|
|
||||||
const resolved = resolve(packageRoot, entry);
|
|
||||||
if (existsSync(resolved)) {
|
|
||||||
this.addPath(target, resolved);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Has patterns - enumerate all files and filter
|
// Then apply user patterns on top
|
||||||
const allFiles = this.collectAllPackageFiles(packageRoot, resourceType);
|
const filtered = applyPatterns(manifestFiles, userPatterns, packageRoot);
|
||||||
const filtered = applyPatterns(allFiles, patterns, packageRoot);
|
|
||||||
for (const f of filtered) {
|
for (const f of filtered) {
|
||||||
this.addPath(target, f);
|
this.addPath(target, f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect all files of a given resource type from a package.
|
* Collect files that the manifest provides, with manifest patterns applied.
|
||||||
|
* This is what the package makes available before user filtering.
|
||||||
*/
|
*/
|
||||||
private collectAllPackageFiles(packageRoot: string, resourceType: ResourceType): string[] {
|
private collectManifestFilteredFiles(packageRoot: string, resourceType: ResourceType): string[] {
|
||||||
const manifest = this.readPiManifest(packageRoot);
|
const manifest = this.readPiManifest(packageRoot);
|
||||||
|
|
||||||
// If manifest specifies paths, use those
|
|
||||||
if (manifest) {
|
if (manifest) {
|
||||||
const manifestPaths = manifest[resourceType];
|
const manifestEntries = manifest[resourceType];
|
||||||
if (manifestPaths && manifestPaths.length > 0) {
|
if (manifestEntries && manifestEntries.length > 0) {
|
||||||
const files: string[] = [];
|
// Enumerate all files from non-pattern entries
|
||||||
for (const p of manifestPaths) {
|
const allFiles: string[] = [];
|
||||||
const resolved = resolve(packageRoot, p);
|
for (const entry of manifestEntries) {
|
||||||
|
if (isPattern(entry)) continue;
|
||||||
|
|
||||||
|
const resolved = resolve(packageRoot, entry);
|
||||||
if (!existsSync(resolved)) continue;
|
if (!existsSync(resolved)) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stats = statSync(resolved);
|
const stats = statSync(resolved);
|
||||||
if (stats.isFile()) {
|
if (stats.isFile()) {
|
||||||
files.push(resolved);
|
allFiles.push(resolved);
|
||||||
} else if (stats.isDirectory()) {
|
} else if (stats.isDirectory()) {
|
||||||
if (resourceType === "skills") {
|
if (resourceType === "skills") {
|
||||||
files.push(...collectSkillEntries(resolved));
|
allFiles.push(...collectSkillEntries(resolved));
|
||||||
} else {
|
} else {
|
||||||
files.push(...collectFiles(resolved, FILE_PATTERNS[resourceType]));
|
allFiles.push(...collectFiles(resolved, FILE_PATTERNS[resourceType]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore errors
|
// Ignore errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return files;
|
|
||||||
|
// Apply manifest patterns (if any)
|
||||||
|
const manifestPatterns = manifestEntries.filter(isPattern);
|
||||||
|
if (manifestPatterns.length > 0) {
|
||||||
|
return applyPatterns(allFiles, manifestPatterns, packageRoot);
|
||||||
|
}
|
||||||
|
return allFiles;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,44 @@ Content`,
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("pattern filtering in package filters", () => {
|
describe("pattern filtering in package filters", () => {
|
||||||
|
it("should apply user filters on top of manifest filters (not replace)", async () => {
|
||||||
|
// Manifest excludes baz.ts, user excludes bar.ts
|
||||||
|
// Result should exclude BOTH
|
||||||
|
const pkgDir = join(tempDir, "layered-pkg");
|
||||||
|
mkdirSync(join(pkgDir, "extensions"), { recursive: true });
|
||||||
|
writeFileSync(join(pkgDir, "extensions", "foo.ts"), "export default function() {}");
|
||||||
|
writeFileSync(join(pkgDir, "extensions", "bar.ts"), "export default function() {}");
|
||||||
|
writeFileSync(join(pkgDir, "extensions", "baz.ts"), "export default function() {}");
|
||||||
|
writeFileSync(
|
||||||
|
join(pkgDir, "package.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
name: "layered-pkg",
|
||||||
|
pi: {
|
||||||
|
extensions: ["extensions", "!**/baz.ts"],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// User filter adds exclusion for bar.ts
|
||||||
|
settingsManager.setPackages([
|
||||||
|
{
|
||||||
|
source: pkgDir,
|
||||||
|
extensions: ["!**/bar.ts"],
|
||||||
|
skills: [],
|
||||||
|
prompts: [],
|
||||||
|
themes: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const result = await packageManager.resolve();
|
||||||
|
// foo.ts should be included (not excluded by anyone)
|
||||||
|
expect(result.extensions.some((p) => p.endsWith("foo.ts"))).toBe(true);
|
||||||
|
// bar.ts should be excluded (by user)
|
||||||
|
expect(result.extensions.some((p) => p.endsWith("bar.ts"))).toBe(false);
|
||||||
|
// baz.ts should be excluded (by manifest)
|
||||||
|
expect(result.extensions.some((p) => p.endsWith("baz.ts"))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("should exclude extensions from package with ! pattern", async () => {
|
it("should exclude extensions from package with ! pattern", async () => {
|
||||||
const pkgDir = join(tempDir, "pattern-pkg");
|
const pkgDir = join(tempDir, "pattern-pkg");
|
||||||
mkdirSync(join(pkgDir, "extensions"), { recursive: true });
|
mkdirSync(join(pkgDir, "extensions"), { recursive: true });
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue