mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
Fixes #10488 ## Problem On Windows, `c:\Users\me` and `C:\Users\me` are the same directory, but pi compared paths as exact strings (`===`, `startsWith`, map and set keys). `realpathSync` keeps the input casing, so canonicalizing does not help. The reported case, with cwd `c:\Users\me\project` and home `C:\Users\me`: 1. Ancestor discovery finds `c:\Users\me\.agents\skills`. The exclusion of the user dir `C:\Users\me\.agents\skills` does not match, so global skills load as project skills. 2. The same dir loads again as user skills. Dedup does not match either, so every skill is reported as a name collision. 3. `hasTrustRequiringProjectResources` treats `~/.agents/skills` as a project resource that requires trust. The same pattern affected other places: - Saved trust decisions in `trust.json` were not found when cwd casing differed, so pi asked for trust again. - With a custom session dir, `--continue`, resume by id, and the session list hid sessions recorded with different cwd casing. - Git worktree `AGENTS.md` shadowing failed when cwd casing differed from git's paths. - Local package matching (`pi remove c:\pkg` vs configured `C:\pkg`) and package identity across global and project settings. - Extension preload matching, skill scope classification, resource source info, the config and session selector keys, and the session import self-check. Separately, home-to-`~` shortening used an unanchored `startsWith(home)` in four places, so `/home/me2/x` was shown as `~2/x`. ## Fix New helpers in `utils/paths.ts`, all based on one comparison key: - `getPathComparisonKey(path)`: the resolved path, lowercased on Windows. It does not touch the filesystem. - `pathsEqual(a, b)` - `isPathWithin(parent, child)`: true for equal paths or paths inside `parent`, matching whole path segments. - `shortenHomePath(path, home?)`: replaces a leading home dir with `~`. The helpers pick `path.win32` or `path.posix` from `process.platform` at call time, so tests can exercise real Windows paths (drive letters, UNC). `getResourcePathKey(path)` in `source-info.ts` is the dedup key for resources. Synthetic identifiers (`builtin:<name>`, `<inline:...>`) stay unchanged, because they are not filesystem paths and their lookup is case-sensitive. File paths go through realpath plus the comparison key. The helpers are used only for comparison; stored and displayed paths are unchanged. Trust store lookups and `set` match keys case-insensitively on Windows. Unchanged on purpose: - The package install-root guards (`resolveManagedPath`, `pruneEmptyGitParents`, the `node_modules` dependency check) keep exact string checks. Both sides come from the same root string, and exact is the stricter option. - Checks based on `path.relative()` (`getCwdRelativePath`, the changelog containment check) were already correct: Node's `path.win32.relative` ignores case. NTFS can enable per-directory case sensitivity, but it is rarely used outside WSL interop, so it is ignored (see the comment on `getPathComparisonKey`). ## Tests - `test/paths.test.ts`: Windows drive-letter, UNC and root cases, POSIX case sensitivity, segment-boundary containment, and home shortening, including root and UNC homes. - `test/source-info.test.ts`: file paths fold case on Windows; builtin and inline identifiers keep their case. - `test/trust-manager.test.ts`: the `.agents/skills` exclusion with a lowercase `HOME`, plus saved trust decisions matched and replaced across casing (simulated win32). An end-to-end test of the reported loader scenario is not feasible on Linux: the loader resolves paths with POSIX `node:path`, and the filesystem is case-sensitive. `npm run check` passes. The coding-agent suite passes, except for 13 failures that also occur without this change: clipboard tool detection in this environment, and one test that needs built `dist/` output.
19 lines
923 B
TypeScript
19 lines
923 B
TypeScript
import { afterEach, describe, expect, it } from "vitest";
|
|
import { getResourcePathKey } from "../src/core/source-info.ts";
|
|
|
|
describe("getResourcePathKey", () => {
|
|
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
|
|
|
|
afterEach(() => {
|
|
if (originalPlatform) Object.defineProperty(process, "platform", originalPlatform);
|
|
});
|
|
|
|
// Regression test for https://github.com/earendil-works/pi/issues/10488
|
|
it("folds file path casing on Windows but keeps synthetic identifiers unchanged", () => {
|
|
Object.defineProperty(process, "platform", { value: "win32" });
|
|
expect(getResourcePathKey("C:\\Users\\Me\\ext.ts")).toBe(getResourcePathKey("c:\\users\\me\\ext.ts"));
|
|
expect(getResourcePathKey("builtin:MCP")).toBe("builtin:MCP");
|
|
expect(getResourcePathKey("builtin:MCP")).not.toBe(getResourcePathKey("builtin:mcp"));
|
|
expect(getResourcePathKey("<inline:Name>")).toBe("<inline:Name>");
|
|
});
|
|
});
|