fix(coding-agent): expand ~ in resource setting patterns

Entries like !~/skills/pdf, +~/x and -~/x in the extensions, skills,
prompts, and themes settings were compared with a literal ~ and never
matched.

fixes #10765
This commit is contained in:
David Brailovsky 2026-10-12 14:02:31 +02:00
parent 6db4070f17
commit 6a51a39d42
3 changed files with 41 additions and 3 deletions

View file

@ -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

View file

@ -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 {

View file

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