diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index d427add2e..998deff3d 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed the `omp grep` CLI subcommand failing on paths with a stray leading colon (e.g. `:/abs/path`); it now routes the path argument through `expandPath` like `read`/`edit`/in-agent `grep` ([#5624](https://github.com/can1357/oh-my-pi/issues/5624)). +- Fixed the `omp grep` CLI subcommand failing on paths with a stray leading colon (e.g. `:/abs/path`); it now routes the path argument through `expandPath` like `read`/`edit`/in-agent `grep`. Broadened `expandPath`'s leading-colon strip to also recover Windows-style shapes (`:C:\repo\file`, `:.\src`, `:..\rel`, `:\\server\share`) ([#5624](https://github.com/can1357/oh-my-pi/issues/5624)). ## [17.0.0] - 2026-07-15 diff --git a/packages/coding-agent/src/tools/path-utils.ts b/packages/coding-agent/src/tools/path-utils.ts index 9da7262c0..5602b3c85 100644 --- a/packages/coding-agent/src/tools/path-utils.ts +++ b/packages/coding-agent/src/tools/path-utils.ts @@ -148,11 +148,14 @@ export function expandTilde(filePath: string, home?: string): string { export function expandPath(filePath: string): string { // Some models intermittently prefix an otherwise-valid path with a stray - // `:` (e.g. `:/abs/path`, `:../rel`). No real path starts with `:` and it - // never begins a selector against an absolute/relative path, so strip it - // before resolution — mirroring the `@`-prefix normalization above and the - // implicit stripping `write` already tolerates (issue #5508). - const deColoned = /^:(?=[/~]|\.\.?\/)/.test(filePath) ? filePath.slice(1) : filePath; + // `:` (e.g. `:/abs/path`, `:../rel`, or the Windows forms `:C:\repo\file` + // and `:.\src`). No real path starts with `:` and it never begins a + // selector against an absolute/relative path, so strip it before + // resolution — mirroring the `@`-prefix normalization above and the + // implicit stripping `write` already tolerates (issues #5508, #5624). The + // lookahead admits POSIX (`/`, `~`, `./`, `../`) and Windows (`\`, `.\`, + // `..\`, drive-letter `C:`) path shapes. + const deColoned = /^:(?=[/\\~]|\.\.?[/\\]|[A-Za-z]:)/.test(filePath) ? filePath.slice(1) : filePath; const normalized = stripWindowsExtendedLengthPathPrefix( stripFileUrl(normalizeUnicodeSpaces(normalizeAtPrefix(deColoned))), ); diff --git a/packages/coding-agent/test/tools/path-literal-colon-selector.test.ts b/packages/coding-agent/test/tools/path-literal-colon-selector.test.ts index c11cc0bd9..e79abfbde 100644 --- a/packages/coding-agent/test/tools/path-literal-colon-selector.test.ts +++ b/packages/coding-agent/test/tools/path-literal-colon-selector.test.ts @@ -6,6 +6,7 @@ import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config import { EditTool } from "@oh-my-pi/pi-coding-agent/edit"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { + expandPath, probeLiteralPathExists, resolveToCwd, splitPathAndSel, @@ -363,6 +364,23 @@ describe("leading-colon path recovery (issue #5508)", () => { expect(resolveToCwd(":name.txt", tmpDir)).toBe(path.join(tmpDir, ":name.txt")); }); + it("strips a leading colon before Windows path shapes in expandPath (issue #5624)", () => { + // Windows native paths mangled with a stray leading colon: drive-letter + // absolutes and `\`/`.\`/`..\` relative forms. expandPath runs before any + // path.resolve, so the strip is platform-independent. + expect(expandPath(":C:\\repo\\file.ts")).toBe("C:\\repo\\file.ts"); + expect(expandPath(":.\\src")).toBe(".\\src"); + expect(expandPath(":..\\sibling")).toBe("..\\sibling"); + expect(expandPath(":\\\\server\\share")).toBe("\\\\server\\share"); + }); + + it("does not strip a colon before a bare drive letter without a path (expandPath)", () => { + // `:selector` shapes still round-trip; the drive-letter branch requires + // the `:` colon to follow, distinguishing `:C:\x` from `:cache`. + expect(expandPath(":raw")).toBe(":raw"); + expect(expandPath(":cache")).toBe(":cache"); + }); + it("read opens a file addressed with a leading colon", async () => { const abs = path.join(tmpDir, "colon-read.txt"); await Bun.write(abs, "test line A\ntest line B\n");