fix(cli): Support Windows-style tilde paths#6029
Conversation
|
@qwen-code /triage |
|
Thanks for the PR, @VectorPeak! Template looks good ✓ — all required headings present, bilingual, solid test evidence. On direction: this is a straightforward fix for a real Windows usability issue. On approach: the right move. The PR correctly identifies that tilde/ Moving on to code review and testing. 🔍 中文说明感谢贡献,@VectorPeak! 模板完整 ✓ — 所有必需的标题都在,中英双语,测试证据充分。 方向:这是一个直截了当的修复,解决了 Windows 用户的实际痛点。 方案:正确的做法。PR 正确识别了 tilde/ 进入代码审查和测试阶段。🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal (before reading the diff): I'd add Comparison: The PR matches this proposal and goes further in a good way — it introduces a private No blocking issues found. A few observations:
Test ResultsAll affected test suites pass on Linux: Terminal capturesCore paths testsCLI cdCommand testsCLI directoryCommand testsCLI resolvePath testsTypecheckNote on tmux E2E testing: The core change ( 中文说明代码审查独立方案(阅读 diff 之前):我会在 对比: PR 与我的方案一致,并且以好的方式更进一步——引入了私有的 未发现阻断性问题。 测试结果所有受影响的测试套件在 Linux 上均通过:core paths 126 通过 1 跳过,cdCommand 20 通过,directoryCommand 27 通过,resolvePath 7 通过。Typecheck 全部 7 个包干净通过。 关于 tmux E2E 测试的说明: 核心变更( — Qwen Code · qwen3.7-max |
|
Stepping back to look at the whole picture: this PR does exactly what it should. The problem is real — Windows users type My independent proposal matched the PR's approach closely, which is a good sign — there's an obvious right way to do this and the author found it. The diff doesn't carry any unrelated changes or scope creep. The All 180 tests across the 4 affected suites pass. Typecheck is clean across all packages. The code is straightforward — If I had to maintain this in six months, I'd thank the author for consolidating three scattered implementations into one place with clear intent. Approving. ✅ 中文说明退一步看整体:这个 PR 做到了它应该做的。问题是真实的——Windows 用户自然输入 我的独立方案与 PR 的方法高度一致,这是个好信号——存在一个显而易见的正确做法,而作者找到了它。diff 没有夹带任何无关改动或范围蔓延。 4 个受影响测试套件共 180 个测试全部通过。所有包的 typecheck 干净。代码直截了当—— 如果六个月后需要维护这段代码,我会感谢作者将三处分散的实现整合到了一个地方,意图清晰。 批准。✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
doudouOUC
left a comment
There was a problem hiding this comment.
[Suggestion] The PR patches 2 of ~16 tilde-expansion sites across the codebase. Other locations (prompts.ts, openaiLogger.ts, rule-parser.ts, text-buffer.ts, desktop path handlers) still lack ~\ support. Consider extracting a shared expandTilde(input: string): string utility that handles ~, ~/, and ~\ in one place, then migrating all call sites — this eliminates the whack-a-mole pattern where every new path-handling site must independently remember to check both separators.
— qwen3.7-max via Qwen Code /review
| @@ -329,7 +329,7 @@ export function resolvePath( | |||
|
|
|||
| if (relativePath === '~') { | |||
| return homeDir; | |||
| } else if (relativePath.startsWith('~/')) { | |||
| } else if (relativePath.startsWith('~/') || relativePath.startsWith('~\\')) { | |||
There was a problem hiding this comment.
[Critical] POSIX backslash normalization bug. On macOS/Linux, path.join(homeDir, relativePath.slice(2)) does NOT convert backslashes — they become literal filename characters. Input ~\src\components resolves to /home/user/src\components (broken) instead of /home/user/src/components. The same bug exists in cdCommand.ts:43.
The codebase already has 6+ correct implementations using .split(/[\/\\]+/) before path.join (e.g., packages/cli/src/utils/resolvePath.ts:19-25, packages/core/src/config/storage.ts:59-67). Adopt the proven pattern:
| } else if (relativePath.startsWith('~/') || relativePath.startsWith('~\\')) { | |
| } else if (relativePath.startsWith('~/') || relativePath.startsWith('~\\')) { | |
| return path.join(homeDir, ...relativePath.slice(2).split(/[\/\\]+/).filter(Boolean)); |
Apply the same fix in cdCommand.ts:43:
if (input.startsWith('~/') || input.startsWith('~\\')) {
const segments = input.slice(2).split(/[\/\\]+/).filter(Boolean);
return path.normalize(path.join(os.homedir(), ...segments));
}— qwen3.7-max via Qwen Code /review
| it('expands Windows-style tilde-prefixed paths to home directory', () => { | ||
| const homeDir = os.homedir(); | ||
| const result = resolvePath('/some/base', '~\\documents\\file.txt'); | ||
| expect(result).toBe(path.join(homeDir, 'documents\\file.txt')); |
There was a problem hiding this comment.
[Critical] Test validates broken behavior on POSIX. Both the implementation and path.join(homeDir, 'documents\\file.txt') produce the same malformed path (/home/user/documents\file.txt) on macOS/Linux — a path with a literal backslash in the filename. The test passes on POSIX but for the wrong reason.
A correct assertion would expose the bug by expecting proper path joining:
| expect(result).toBe(path.join(homeDir, 'documents\\file.txt')); | |
| expect(result).toBe(path.join(homeDir, 'documents', 'file.txt')); |
This assertion will FAIL on POSIX with the current implementation (exposing the bug) and PASS after applying the split(/[\/\\]+/) fix.
— qwen3.7-max via Qwen Code /review
| @@ -205,6 +205,23 @@ describe('cdCommand', () => { | |||
| }); | |||
| }); | |||
|
|
|||
| it('resolves a Windows-style home-relative path from the home directory', async () => { | |||
There was a problem hiding this comment.
[Suggestion] This test only covers the error path (directory not found). No success test verifies that cd ~\existingDir actually calls relocateWorkingDirectory. Consider adding a success-path test mirroring the existing ../next test pattern:
it('moves to a Windows-style home-relative directory', async () => {
const homeSub = path.join(os.homedir(), `qwen-cd-ok-${process.pid}`);
fs.mkdirSync(homeSub);
try {
const result = await cdCommand.action?.(context, `~\\qwen-cd-ok-${process.pid}`);
expect(relocateWorkingDirectory).toHaveBeenCalledWith(homeSub, context);
} finally {
fs.rmSync(homeSub, { recursive: true });
}
});— qwen3.7-max via Qwen Code /review
There was a problem hiding this comment.
Thanks for the suggestion. This is covered now by the success-path Windows-style home-relative /cd test, and the targeted cdCommand.test.ts -t "Windows-style home-relative" run passed: 2 passed, 18 skipped.
wenshao
left a comment
There was a problem hiding this comment.
[Suggestion] directoryCommand.tsx:30 — The CLI's local expandHomeDir() in directoryCommand.tsx (used by /directory add) still only checks p === '~' || p.startsWith('~/') without ~\. A Windows user typing /directory add ~\Projects would get a literal ~\Projects path instead of home-directory expansion. This is the same class of user this PR targets.
Additionally, a grep for startsWith('~/') across the codebase shows ~16 other tilde-expansion sites that still lack ~\ support (e.g. rule-parser.ts:808 for permission path specifiers, openaiLogger.ts:34, core/prompts.ts:42). Consider extracting a single expandTilde(input): string helper and routing all call sites through it, which would also address the normalization inconsistency noted inline.
— qwen3.7-max via Qwen Code /review
| it('expands Windows-style tilde-prefixed paths to home directory', () => { | ||
| const homeDir = os.homedir(); | ||
| const result = resolvePath('/some/base', '~\\documents\\file.txt'); | ||
| expect(result).toBe(path.join(homeDir, 'documents\\file.txt')); |
There was a problem hiding this comment.
[Suggestion] This test encodes a POSIX-incorrect result. On macOS/Linux, path.join(homeDir, 'documents\\file.txt') produces a path with literal backslashes in the filename (e.g. /home/user/documents\file.txt), not a nested directory path. On Windows, path.join normalizes backslashes so the result happens to be correct.
The codebase already has a more robust pattern in channels/base/src/paths.ts and cli/src/utils/resolvePath.ts that splits on both separators before joining:
path.join(homeDir, ...relativePath.slice(2).split(/[/\\]+/).filter(Boolean))This normalizes separators correctly on all platforms. Consider adopting this pattern in both resolvePath() (paths.ts:332) and resolveCdPath() (cdCommand.ts:44), and updating the test assertion accordingly.
— qwen3.7-max via Qwen Code /review
|
Addressed the shared-utility concern in 16ccc97. What changed:
I intentionally kept this scoped to the Qwen core/CLI path-resolution layer rather than migrating every Validation:
|
|
Hi @doudouOUC and @wenshao, thank you both very much for the careful review and the helpful suggestions. I believe the shared tilde-expansion concern has now been addressed in
Validation I ran:
When you have time, could you please take another look? I would really appreciate your re-review. |
|
@qwen-code /review |
| _Qwen Code review request accepted. Review is queued in [workflow run](https://github.com/QwenLM/qwen-code/actions/runs/28424692918)._ |
doudouOUC
left a comment
There was a problem hiding this comment.
Both prior Critical findings are resolved: (1) expandTilde() now uses the split(/[/\\]+/) pattern for proper cross-platform normalization, (2) tests correctly verify home-directory expansion on both POSIX and Windows. The consolidation of cdCommand, directoryCommand, and resolvePath onto the shared utility is clean. LGTM! ✅
— qwen3.7-max via Qwen Code /review
| import { | ||
| loadServerHierarchicalMemory, | ||
| ConditionalRulesRegistry, | ||
| expandHomeDir, |
There was a problem hiding this comment.
[Suggestion] getDirPathCompletions (line ~78) still uses inline trimmed.replace(/^~/, os.homedir()) for tilde expansion. This regex doesn't recognize ~\ as home-relative, so tab-completing ~\project produces no suggestions on any platform — even though /cd ~\project and /directory add ~\project now work correctly after this PR.
Since this file was already modified to import expandHomeDir from core, the completion function could use the same shared utility:
| expandHomeDir, | |
| import { | |
| expandHomeDir, | |
| expandTilde, | |
| } from '@qwen-code/qwen-code-core'; |
Then in getDirPathCompletions, replace the inline replace(/^~/, ...) with expandTilde(trimmed).
— qwen3.7-max via Qwen Code /review
There was a problem hiding this comment.
Thanks for the catch. The completion path now uses the public expandHomeDir(trimmed) helper rather than the inline trimmed.replace(/^~/, os.homedir()), so ~\\... completion follows the same path expansion as /cd and /directory add. Targeted validation after the latest update: directoryCommand.test.tsx passed: 27 passed.
| return os.homedir(); | ||
| } | ||
| if (p === '~/' || p === '~\\') { | ||
| return os.homedir() + path.sep; |
There was a problem hiding this comment.
[Suggestion] Trailing-separator inconsistency: expandTilde('~/') returns homedir + path.sep (with trailing /), but expandHomeDir('~/') wraps the result in path.normalize(), which strips the trailing separator. Core resolvePath calls expandTilde directly without normalizing, so its callers get a path with trailing separator while expandHomeDir callers don't.
This creates a subtle API footgun — path.dirname(expandTilde('~/')) and path.dirname(expandHomeDir('~/')) return different directories. Consider either normalizing in expandTilde as well, or documenting the trailing-separator contract explicitly in both functions' JSDoc.
— qwen3.7-max via Qwen Code /review
| }); | ||
|
|
||
| it('should expand Windows-style ~\\path to home directory path', () => { | ||
| expect(expandTilde('~\\documents')).toBe(path.join(homeDir, 'documents')); |
There was a problem hiding this comment.
[Suggestion] Test gap: the expandTilde tests don't cover mixed separators (e.g., expandTilde('~/foo\\bar/baz')). The mixed-separator splitting via .split(/[/\\]+/) is the core of the cross-platform fix — if a future refactor changes the regex to split('/') only, no test would catch the regression.
it('should handle mixed separators', () => {
expect(expandTilde('~/foo\\bar/baz')).toBe(path.join(homeDir, 'foo', 'bar', 'baz'));
});Also consider a test for resolvePath('/base', '') (empty relativePath), which exercises the expandTilde('') → '' → path.resolve(baseDir, '') path.
— qwen3.7-max via Qwen Code /review
| * @param p - The path to expand. | ||
| * @returns The expanded path. | ||
| */ | ||
| export function expandTilde(p: string): string { |
There was a problem hiding this comment.
[Suggestion] expandTilde is exported from utils/paths.ts, and packages/core/src/index.ts re-exports all of utils/paths.js, so this becomes public API. The only production uses are inside this same module (expandHomeDir and resolvePath); external callers continue to use expandHomeDir. Since this is an implementation detail for those helpers, keeping it private avoids adding another home-expansion API surface.
Suggested fix: remove export from expandTilde, and keep tests focused on the public expandHomeDir / resolvePath behavior unless another package needs tilde-only expansion.
— GPT-5 via Qwen Code /review
| if (p === '~/' || p === '~\\') { | ||
| return os.homedir() + path.sep; | ||
| } | ||
| if (p.startsWith('~/') || p.startsWith('~\\')) { |
There was a problem hiding this comment.
[Suggestion] This branch now splits the suffix on both / and \\ for the existing ~/... form, not just for the new ~\\... form. On POSIX, ~/foo\\bar used to resolve to a filename containing a literal backslash under $HOME, but now resolves to $HOME/foo/bar. The new Windows-style ~\\... support can normalize backslashes without changing how the already-supported ~/... spelling treats literal backslashes.
Suggested fix: branch by prefix so ~/... preserves the old path.join(os.homedir(), p.slice(2)) behavior, and apply the / + \\ segment splitting only to the new ~\\... case.
— GPT-5 via Qwen Code /review
| return ''; | ||
| } | ||
| let expandedPath = p; | ||
| if (p.toLowerCase().startsWith('%userprofile%')) { |
There was a problem hiding this comment.
[Suggestion] startsWith('%userprofile%') also matches values where the placeholder is not followed by a separator, but the new path.join(...split(...)) treats the remaining text as path segments. That changes legacy inputs such as %USERPROFILE%foo from <home>foo to <home>/foo, and %USERPROFILE%..\\outside can normalize through .. to the parent of the home directory. The segmented join should only run for the exact placeholder or separator-prefixed paths.
Suggested fix: first handle exact %USERPROFILE%, then handle %USERPROFILE%/ and %USERPROFILE%\\ with segment splitting; for other prefixes, preserve the previous literal concatenation/normalization behavior or leave the input unchanged.
— GPT-5 via Qwen Code /review
|
Hi @wenshao and @doudouOUC, thank you again for the careful follow-up review. I pushed another update in
Validation I ran locally:
I also reran the full When you have time, could you please take another look? I really appreciate the detailed review. |
|
@qwen-code /triage |
|
Follow-up CI fix pushed in The failing Ubuntu check was caused by This update restores the legacy Local validation:
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
This PR is very close. The core fix — supporting ~\ as a home-relative path — works correctly, and the consolidation into a shared expandTilde() helper is the right architectural move. The before/after comparison clearly shows ~\docs now resolving to $HOME/docs instead of being treated as a relative path.
But there's a real regression: expandHomeDir('~/') drops the trailing slash that the old code preserved. This breaks a pre-existing test on main (keeps the existing POSIX-style trailing separator behavior). The fix is straightforward — expandTilde('~/') needs to preserve the trailing separator, e.g.:
if (p === '~/' || p === '~\\') {
return os.homedir() + path.sep;
}Once that's fixed, this is good to ship. Requesting changes for this one issue.
中文说明
这个 PR 非常接近了。核心修复——支持 ~\ 作为 home-relative 路径——工作正常,整合到共享的 expandTilde() 辅助函数是正确的架构选择。修改前后对比清楚地展示了 ~\docs 现在正确解析到 $HOME/docs,而不是被当作相对路径。
但存在一个真实的回归:expandHomeDir('~/') 丢失了旧代码保留的尾部斜杠。这破坏了 main 上一个已有的测试。修复很简单——expandTilde('~/') 需要保留尾部分隔符。
修复后即可合并。针对这一个问题请求修改。
— Qwen Code · qwen3.7-max
|
Hi @wenshao and @doudouOUC, thank you again for the follow-up review. The trailing-separator regression called out by the latest automated review has been addressed in the current PR head, Specifically, the private tilde helper now preserves exact home-directory separator inputs: if (p === '~/' || p === '~\\') {
return os.homedir() + path.sep;
}I also added public expect(expandHomeDir('~/')).toBe(path.normalize(homeDir + path.sep));
expect(expandHomeDir('~\\')).toBe(path.normalize(homeDir + path.sep));Local validation after this fix:
The Ubuntu CI job is still running at the moment. Once it completes, could you please take another look? I really appreciate your time and the detailed review. |
|
Quick update: the GitHub Actions run has completed successfully now.
So the trailing-separator fix is now covered by both the focused local tests above and the full Ubuntu CI signal. @wenshao @doudouOUC, could you please re-review when convenient? Thank you again. |
| ...p | ||
| .substring(2) | ||
| .split(/[/\\]+/) | ||
| .filter(Boolean), |
There was a problem hiding this comment.
[Suggestion] Trailing separator stripping in the ~\ branch: .split(/[/\\]+/).filter(Boolean) discards empty segments produced by trailing separators, so expandTilde('~\\path\\') returns homedir/path (no trailing sep) while the sibling ~/ branch (path.join(homedir, p.substring(2))) preserves them — expandTilde('~/path/') returns homedir/path/. This asymmetry means ~\docs\ and ~/docs/ produce different shapes after expansion, which can surprise callers that use trailing separators to signal "directory".
Consider detecting and re-attaching a trailing separator:
| .filter(Boolean), | |
| const rest = p.substring(2); | |
| const hasTrailingSep = rest.endsWith('/') || rest.endsWith('\\'); | |
| const joined = path.join( | |
| os.homedir(), | |
| ...rest.split(/[/\\]+/).filter(Boolean), | |
| ); | |
| return hasTrailingSep ? joined + path.sep : joined; |
The same pattern also appears in the %userprofile%/ branch of expandHomeDir (line 136) and has the same issue — expandHomeDir('%userprofile%/') strips the trailing separator while expandHomeDir('~/') preserves it via expandTilde's explicit guard.
— qwen3.7-max via Qwen Code /review
There was a problem hiding this comment.
Thanks for calling this out. Addressed in 591c8e5f5: the ~\\... branch now detects a trailing / or \\ before segment splitting and re-attaches path.sep after path.join(...), so ~\\docs\\ keeps the same trailing-directory signal as ~/docs/.
Focused validation: cd packages/core && npx vitest run src/utils/paths.test.ts --coverage=false passed: 115 passed, 12 skipped.
| ...p | ||
| .substring(userProfilePrefix.length + 1) | ||
| .split(/[/\\]+/) | ||
| .filter(Boolean), |
There was a problem hiding this comment.
[Suggestion] expandHomeDir('%userprofile%/') strips the trailing separator because p.substring(userProfilePrefix.length + 1) skips the separator character and .split(/[/\\]+/).filter(Boolean) then discards the empty trailing segment. Compare with expandHomeDir('~/') which correctly returns homedir + path.sep via expandTilde's explicit guard.
Add a dedicated early-return for the bare-prefix-with-separator case, mirroring expandTilde's ~/ guard:
| .filter(Boolean), | |
| if (lowerPath === `${userProfilePrefix}/` || lowerPath === `${userProfilePrefix}\\`) { | |
| return path.normalize(os.homedir() + path.sep); | |
| } | |
| if ( | |
| lowerPath.startsWith(`${userProfilePrefix}/`) || | |
| lowerPath.startsWith(`${userProfilePrefix}\\`) | |
| ) { | |
| const rest = p.substring(userProfilePrefix.length + 1); | |
| const hasTrailingSep = rest.endsWith('/') || rest.endsWith('\\'); | |
| const joined = path.join( | |
| os.homedir(), | |
| ...rest.split(/[/\\]+/).filter(Boolean), | |
| ); | |
| return path.normalize(hasTrailingSep ? joined + path.sep : joined); | |
| } |
— qwen3.7-max via Qwen Code /review
There was a problem hiding this comment.
Thanks, fixed in 591c8e5f5. I added the dedicated %USERPROFILE%/ and %USERPROFILE%\\ early return, and the separator-prefixed %USERPROFILE%/... branch now preserves trailing separators for suffixed paths before normalizing.
This keeps the previous exact %USERPROFILE% and legacy %USERPROFILE%foo behavior intact while making the separator-prefixed forms consistent with ~/ and ~\\.
| @@ -946,7 +981,13 @@ describe('expandHomeDir', () => { | |||
|
|
|||
| it('should expand %userprofile%\\path to home directory path', () => { | |||
| const result = expandHomeDir('%userprofile%\\documents'); | |||
There was a problem hiding this comment.
[Suggestion] The expandHomeDir code has an explicit branch for both %userprofile%/ (forward-slash) and %userprofile%\\ (backslash) — see paths.ts lines 129-130 — but only the backslash variant is tested here. Consider adding a forward-slash test to cover the new branch:
it('should expand %USERPROFILE%/path with forward-slash separator', () => {
expect(expandHomeDir('%USERPROFILE%/documents')).toBe(
path.join(homeDir, 'documents'),
);
});— qwen3.7-max via Qwen Code /review
There was a problem hiding this comment.
Added this coverage in 591c8e5f5:
expect(expandHomeDir('%USERPROFILE%/documents')).toBe(
path.join(homeDir, 'documents'),
);I also added coverage for %USERPROFILE%/ and %USERPROFILE%\\documents\\ preserving trailing separators, since that was the related behavior gap in the implementation branch.
|
Hi @wenshao and @doudouOUC, thank you again for the careful follow-up review. I pushed
Local validation after this update:
GitHub Actions is also green for the current head:
When convenient, could you please take another look? I really appreciate your time and the detailed review. |
|
Thanks for the update. I checked current head I'm not seeing new actionable feedback from me at this point. The remaining 中文说明感谢更新。我看了当前 head 我这边目前没有看到新的可执行反馈。剩余的 |
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Local verification report (maintainer)I built and tested this PR locally to support the merge decision. Setup: isolated worktree at head
Results — all green
¹ The 1 skip is the pre-existing Mutation testing — the new tests are load-bearingTo confirm the tests actually guard the behavior (not vacuous), I reverted the fix and re-ran:
Both mutations were reverted; the worktree was byte-identical to the PR head for the final run. Code review (no blocking issues)
Non-blocking notes for the maintainer
VerdictLGTM — correct, well-tested, and safe to merge. Behavior, typecheck, lint, and test coverage all verified locally; mutation testing confirms the suite genuinely guards the fix. 中文版(点击展开)本地验证报告(维护者)我在本地构建并测试了该 PR,作为合并决策的参考。 环境: 在 head
结果——全绿
¹ 这 1 个跳过是既有的 变异测试——新测试确实"承重"为确认这些测试真的在守护行为(而不是空过),我把修复回退后重跑:
两个变异都已还原;最终运行时 worktree 与 PR head 逐字节一致。 代码审查(无阻塞问题)
给维护者的非阻塞说明
结论LGTM——正确、测试充分、可安全合并。 行为、类型检查、lint、测试覆盖均已本地验证;变异测试确认测试套件真正守护了这个修复。 |
What this PR does
This PR makes Windows-style tilde-prefixed paths such as
~\docsresolve from the user's home directory, matching the existing behavior for POSIX-style~/docs.Why it's needed
Qwen Code accepts paths from places where Windows users naturally type backslash-separated paths, including slash commands such as
/cdand shared path-resolution helpers used by the CLI. The existing tilde expansion recognized~and~/..., but it did not recognize~\...as the same home-relative form. As a result,~\docswas parsed as an ordinary relative path whose first directory name was the literal character~, so from a workspace like<workspace>it resolved as<workspace>\~\docsinstead ofC:\Users\<user>\docs. This makes the home-directory shortcut depend on the separator character even though both spellings express the same intent for Windows users.Reviewer Test Plan
How to verify
On Windows, verify that
~\docsresolves to the user's home directory, for exampleC:\Users\<user>\docs, instead of resolving under the current workspace as<workspace>\~\docs. Also verify that the existing~/docsbehavior remains unchanged.Evidence (Before & After)
Before:
~\docsresolved as a relative path with a literal~directory under the current workspace.After:
~\docsresolves as a home-relative path, equivalent to~/docs.Targeted Windows validation run locally:
Result: 106 passed, 12 skipped.
Result: 1 passed, 18 skipped.
git diff --checkResult: passed.
Targeted WSL validation was also run from an isolated Ubuntu-22.04 copy with a fresh Linux-side
npm install, so it did not reuse the Windows-installednode_modulestree:cd packages/core npx vitest run src/utils/paths.test.ts --coverage=falseResult: 117 passed, 1 skipped.
Result: 1 passed, 18 skipped.
Tested on
Environment (optional)
Windows validation used the local Windows checkout. WSL validation used an isolated Linux-side copy with a fresh
npm install; directly reusing the Windows-installednode_modulesfrom WSL is not valid here because Rollup's platform-specific optional dependency for Linux is not present in that install.Risk & Scope
cdCommandtest file was not clean in this Windows environment because an existing symlink test fails withEPERM; the targeted new test passes.Linked Issues
Fixes #6030.
中文说明
这个 PR 做了什么
这个 PR 让 Windows 风格的波浪号路径,例如
~\docs,从用户主目录解析,和已有的 POSIX 风格~/docs行为保持一致。为什么需要
Qwen Code 会从多个入口接收路径输入,包括
/cd这类 slash command,以及 CLI 里共用的路径解析辅助逻辑。在这些地方,Windows 用户很自然会输入反斜杠分隔的路径。原有波浪号展开逻辑识别~和~/...,但没有把~\...识别成同一种 home-relative 写法。结果是,~\docs会被当成普通相对路径,其中第一个目录名是字面量~;如果当前工作区是<workspace>,它会解析成<workspace>\~\docs,而不是C:\Users\<user>\docs。这会让“用户主目录快捷写法”取决于分隔符字符,而这两种写法对 Windows 用户表达的是同一个意图。Reviewer Test Plan
如何验证
在 Windows 上,确认
~\docs会解析到用户主目录,例如C:\Users\<user>\docs,而不是解析到当前工作区下的<workspace>\~\docs。同时确认已有的~/docs行为没有变化。证据(Before & After)
修改前:
~\docs会被解析成当前工作区下带有字面量~目录的相对路径。修改后:
~\docs会被解析成 home-relative 路径,行为等同于~/docs。本地 Windows 针对性验证:
结果:106 passed, 12 skipped。
结果:1 passed, 18 skipped。
git diff --check结果:通过。
同时在 WSL Ubuntu-22.04 的独立 Linux 侧拷贝中,fresh
npm install后运行了针对性验证,避免复用 Windows 安装出来的node_modules:cd packages/core npx vitest run src/utils/paths.test.ts --coverage=false结果:117 passed, 1 skipped。
结果:1 passed, 18 skipped。
Tested on
Environment (optional)
Windows 验证使用本地 Windows checkout。WSL 验证使用独立 Linux 侧拷贝并 fresh
npm install;这里不能直接从 WSL 复用 Windows 安装出来的node_modules,因为该依赖树缺少 Rollup 在 Linux 下需要的平台相关 optional dependency。风险与范围
cdCommand测试文件因为一个既有 symlink 测试出现EPERM,未能作为完整通过项;新增的针对性测试已通过。Linked Issues
Fixes #6030。