diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 631b88d16..3621d3150 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -29,6 +29,7 @@ - Fixed runs started by `pi.sendMessage(..., { triggerTurn: true })` removing system prompt sections added in `before_agent_start` after their first tool call, which changed the prompt mid-run and broke the provider prompt cache twice per run ([#10267](https://github.com/earendil-works/pi/issues/10267)) - Fixed codemode blocking the process for seconds to minutes after scripts with many `text()` calls; joining the output items was quadratic ([#10678](https://github.com/earendil-works/pi/issues/10678)) - Fixed `pi mcp login --help` and the CLI docs describing `--timeout` as only the wait for the browser; it limits the whole sign-in ([#10636](https://github.com/earendil-works/pi/issues/10636)) +- Fixed `~` not being expanded in `!pattern`, `+path`, and `-path` entries of the `extensions`, `skills`, `prompts`, and `themes` settings, so entries like `!~/skills/pdf` matched nothing ([#10765](https://github.com/earendil-works/pi/issues/10765)) ### Removed diff --git a/packages/coding-agent/src/core/package-manager.ts b/packages/coding-agent/src/core/package-manager.ts index b5bb19d74..1ca63ebbb 100644 --- a/packages/coding-agent/src/core/package-manager.ts +++ b/packages/coding-agent/src/core/package-manager.ts @@ -40,7 +40,13 @@ import { gt, maxSatisfying, rcompare, satisfies, valid, validRange } from "semve import { CONFIG_DIR_NAME } from "../config.ts"; import { spawnProcess, spawnProcessSync } from "../utils/child-process.ts"; import { type GitSource, parseGitUrl } from "../utils/git.ts"; -import { canonicalizePath, isLocalPath, markPathIgnoredByCloudSync, resolvePath } from "../utils/paths.ts"; +import { + canonicalizePath, + isLocalPath, + markPathIgnoredByCloudSync, + normalizePath, + resolvePath, +} from "../utils/paths.ts"; import { stripBom } from "../utils/text.ts"; import { isStdoutTakenOver } from "./output-guard.ts"; import { type PiManifest, readPiManifest } from "./pi-manifest.ts"; @@ -669,7 +675,7 @@ function matchesAnyPattern(filePath: string, patterns: string[], baseDir: string const parentDirPosix = isSkillFile ? toPosixPath(parentDir!) : undefined; return patterns.some((pattern) => { - const normalizedPattern = toPosixPath(pattern); + const normalizedPattern = toPosixPath(normalizePath(pattern)); if ( minimatch(rel, normalizedPattern) || minimatch(name, normalizedPattern) || @@ -688,7 +694,7 @@ function matchesAnyPattern(filePath: string, patterns: string[], baseDir: string function normalizeExactPattern(pattern: string): string { const normalized = pattern.startsWith("./") || pattern.startsWith(".\\") ? pattern.slice(2) : pattern; - return toPosixPath(normalized); + return toPosixPath(normalizePath(normalized)); } function matchesAnyExactPattern(filePath: string, patterns: string[], baseDir: string): boolean { diff --git a/packages/coding-agent/test/package-manager.test.ts b/packages/coding-agent/test/package-manager.test.ts index a81054bbe..8aedd93c4 100644 --- a/packages/coding-agent/test/package-manager.test.ts +++ b/packages/coding-agent/test/package-manager.test.ts @@ -1612,6 +1612,37 @@ Content`, }); describe("pattern filtering in top-level arrays", () => { + // #10765 + it("should expand ~ in exclusion and exact patterns", async () => { + const previousHome = process.env.HOME; + process.env.HOME = tempDir; + + try { + const skillsDir = join(tempDir, "extra", "skills"); + for (const name of ["keep", "glob-excluded", "exact-excluded"]) { + mkdirSync(join(skillsDir, name), { recursive: true }); + writeFileSync(join(skillsDir, name, "SKILL.md"), `---\nname: ${name}\ndescription: ${name}\n---\n`); + } + + settingsManager.setSkillPaths([ + "~/extra/skills", + "!~/extra/skills/glob-*", + "-~/extra/skills/exact-excluded", + ]); + + const result = await packageManager.resolve(); + expect(result.skills.some((r) => isEnabled(r, "keep/SKILL.md"))).toBe(true); + expect(result.skills.some((r) => isDisabled(r, "glob-excluded/SKILL.md"))).toBe(true); + expect(result.skills.some((r) => isDisabled(r, "exact-excluded/SKILL.md"))).toBe(true); + } finally { + if (previousHome === undefined) { + delete process.env.HOME; + } else { + process.env.HOME = previousHome; + } + } + }); + it("should exclude extensions with ! pattern", async () => { const extDir = join(agentDir, "extensions"); mkdirSync(extDir, { recursive: true });