From 94fd9d125920442e8cab47dbe14cb9f508bfdf9f Mon Sep 17 00:00:00 2001 From: iliya Date: Sat, 16 May 2026 18:21:14 +0300 Subject: [PATCH] fix: centralize absolute path detection --- src/renderer/store/utils/pathResolution.ts | 16 +++--------- src/renderer/utils/claudeMdTracker.ts | 25 +++++------------- src/renderer/utils/contextTracker.ts | 30 ++++++++-------------- src/shared/utils/platformPath.ts | 12 +++++++++ test/shared/utils/platformPath.test.ts | 14 +++++++++- 5 files changed, 45 insertions(+), 52 deletions(-) diff --git a/src/renderer/store/utils/pathResolution.ts b/src/renderer/store/utils/pathResolution.ts index 8984f710..57771e0c 100644 --- a/src/renderer/store/utils/pathResolution.ts +++ b/src/renderer/store/utils/pathResolution.ts @@ -2,7 +2,7 @@ * Path resolution utilities for the store. */ -import { stripTrailingSeparators } from '@shared/utils/platformPath'; +import { isAbsoluteOrHomePath, stripTrailingSeparators } from '@shared/utils/platformPath'; /** * Resolves a relative path against a base path, handling various path formats. @@ -15,7 +15,7 @@ import { stripTrailingSeparators } from '@shared/utils/platformPath'; */ export function resolveFilePath(base: string, relativePath: string): string { // If already absolute, return as-is - if (isAbsolutePath(relativePath)) { + if (isAbsoluteOrHomePath(relativePath)) { return relativePath; } @@ -27,13 +27,7 @@ export function resolveFilePath(base: string, relativePath: string): string { cleanRelative = cleanRelative.slice(1); } - if (isAbsolutePath(cleanRelative)) { - return cleanRelative; - } - - // Tilde paths (~/) are home-relative absolute paths - pass through as-is - // The main process will expand ~ to the actual home directory - if (cleanRelative.startsWith('~/') || cleanRelative.startsWith('~\\') || cleanRelative === '~') { + if (isAbsoluteOrHomePath(cleanRelative)) { return cleanRelative; } @@ -69,10 +63,6 @@ export function resolveFilePath(base: string, relativePath: string): string { return remainingRelative ? `${normalizedBase}${separator}${remainingRelative}` : normalizedBase; } -function isAbsolutePath(input: string): boolean { - return input.startsWith('/') || input.startsWith('\\\\') || /^[a-zA-Z]:[\\/]/.test(input); -} - function normalizeSeparators(input: string, separator: '/' | '\\'): string { let output = ''; let prevWasSeparator = false; diff --git a/src/renderer/utils/claudeMdTracker.ts b/src/renderer/utils/claudeMdTracker.ts index 661f0e95..7990671a 100644 --- a/src/renderer/utils/claudeMdTracker.ts +++ b/src/renderer/utils/claudeMdTracker.ts @@ -8,6 +8,7 @@ */ import { + isAbsoluteOrHomePath, isPathPrefix, lastSeparatorIndex, normalizePathForComparison, @@ -63,20 +64,6 @@ export function getDisplayName(path: string, _source: ClaudeMdSource): string { return path; } -/** - * Check if a path is absolute (starts with /). - */ -function isAbsolutePath(path: string): boolean { - return ( - path.startsWith('/') || - path.startsWith('~/') || - path.startsWith('~\\') || - path === '~' || - path.startsWith('\\\\') || - /^[a-zA-Z]:[\\/]/.test(path) - ); -} - /** * Join paths, handling various path formats properly. * Handles: @@ -87,7 +74,7 @@ function isAbsolutePath(path: string): boolean { * - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins) */ function joinPaths(base: string, relative: string): string { - if (isAbsolutePath(relative)) { + if (isAbsoluteOrHomePath(relative)) { return relative; } @@ -99,7 +86,7 @@ function joinPaths(base: string, relative: string): string { if (cleanRelative.startsWith('@')) { cleanRelative = cleanRelative.slice(1); } - if (isAbsolutePath(cleanRelative)) { + if (isAbsoluteOrHomePath(cleanRelative)) { return cleanRelative; } @@ -239,7 +226,9 @@ export function extractUserMentionPaths( for (const ref of fileReferences) { if (ref.path) { // Convert to absolute if relative - const absolutePath = isAbsolutePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path); + const absolutePath = isAbsoluteOrHomePath(ref.path) + ? ref.path + : joinPaths(projectRoot, ref.path); paths.push(absolutePath); } } @@ -525,7 +514,7 @@ function computeClaudeMdStats(params: ComputeClaudeMdStatsParams): ClaudeMdStats const responseRefs = extractFileRefsFromResponses(aiGroup.responses); for (const ref of responseRefs) { if (ref.path) { - const absPath = isAbsolutePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path); + const absPath = isAbsoluteOrHomePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path); allFilePaths.push(absPath); } } diff --git a/src/renderer/utils/contextTracker.ts b/src/renderer/utils/contextTracker.ts index e9bd85f9..5e15b55e 100644 --- a/src/renderer/utils/contextTracker.ts +++ b/src/renderer/utils/contextTracker.ts @@ -9,7 +9,11 @@ * This builds on claudeMdTracker.ts and extends it to track all context sources. */ -import { normalizePathForComparison, stripTrailingSeparators } from '@shared/utils/platformPath'; +import { + isAbsoluteOrHomePath, + normalizePathForComparison, + stripTrailingSeparators, +} from '@shared/utils/platformPath'; import { estimateTokens } from '@shared/utils/tokenFormatting'; import { MAX_MENTIONED_FILE_TOKENS } from '../types/contextInjection'; @@ -447,20 +451,6 @@ interface ComputeContextStatsParams { directoryTokenData?: Record; } -/** - * Helper to check if a path is absolute. - */ -function isAbsolutePath(path: string): boolean { - return ( - path.startsWith('/') || - path.startsWith('~/') || - path.startsWith('~\\') || - path === '~' || - path.startsWith('\\\\') || - /^[a-zA-Z]:[\\/]/.test(path) - ); -} - /** * Helper to join paths, handling various path formats properly. * Handles: @@ -471,7 +461,7 @@ function isAbsolutePath(path: string): boolean { * - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins) */ function joinPaths(base: string, relative: string): string { - if (isAbsolutePath(relative)) { + if (isAbsoluteOrHomePath(relative)) { return relative; } @@ -482,7 +472,7 @@ function joinPaths(base: string, relative: string): string { if (cleanRelative.startsWith('@')) { cleanRelative = cleanRelative.slice(1); } - if (isAbsolutePath(cleanRelative)) { + if (isAbsoluteOrHomePath(cleanRelative)) { return cleanRelative; } @@ -679,7 +669,7 @@ function computeContextStats(params: ComputeContextStatsParams): ContextStats { const responseRefs = extractFileRefsFromResponses(aiGroup.responses); for (const ref of responseRefs) { if (ref.path) { - const absPath = isAbsolutePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path); + const absPath = isAbsoluteOrHomePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path); allFilePaths.push(absPath); } } @@ -735,7 +725,7 @@ function computeContextStats(params: ComputeContextStatsParams): ContextStats { if (!fileRef.path) continue; // Convert to absolute path if needed - const absolutePath = isAbsolutePath(fileRef.path) + const absolutePath = isAbsoluteOrHomePath(fileRef.path) ? fileRef.path : joinPaths(projectRoot, fileRef.path); @@ -768,7 +758,7 @@ function computeContextStats(params: ComputeContextStatsParams): ContextStats { for (const fileRef of responseRefs) { if (!fileRef.path) continue; - const absolutePath = isAbsolutePath(fileRef.path) + const absolutePath = isAbsoluteOrHomePath(fileRef.path) ? fileRef.path : joinPaths(projectRoot, fileRef.path); diff --git a/src/shared/utils/platformPath.ts b/src/shared/utils/platformPath.ts index 03eacb2f..9f121087 100644 --- a/src/shared/utils/platformPath.ts +++ b/src/shared/utils/platformPath.ts @@ -22,6 +22,18 @@ export function isWindowsishPath(filePath: string): boolean { return /^[A-Za-z]:\//.test(p) || p.startsWith('//'); } +/** True for filesystem-absolute paths and home-relative `~` paths. */ +export function isAbsoluteOrHomePath(filePath: string): boolean { + return ( + filePath.startsWith('/') || + filePath.startsWith('~/') || + filePath.startsWith('~\\') || + filePath === '~' || + filePath.startsWith('\\\\') || + /^[A-Za-z]:[\\/]/.test(filePath) + ); +} + /** * Normalize for comparisons: * - Convert `\` → `/` diff --git a/test/shared/utils/platformPath.test.ts b/test/shared/utils/platformPath.test.ts index 19d168cc..94979695 100644 --- a/test/shared/utils/platformPath.test.ts +++ b/test/shared/utils/platformPath.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from 'vitest'; -import { getRelativePathWithinPrefix, isPathPrefix } from '../../../src/shared/utils/platformPath'; +import { + getRelativePathWithinPrefix, + isAbsoluteOrHomePath, + isPathPrefix, +} from '../../../src/shared/utils/platformPath'; describe('platformPath Windows containment', () => { it('matches Windows drive paths case-insensitively and preserves child path style', () => { @@ -35,4 +39,12 @@ describe('platformPath Windows containment', () => { expect(isPathPrefix('', '/Users/Alice/Repo/src/app.ts')).toBe(false); expect(getRelativePathWithinPrefix('', '/Users/Alice/Repo/src/app.ts')).toBe(null); }); + + it('detects absolute and home-relative paths across platforms', () => { + expect(isAbsoluteOrHomePath('/Users/Alice/Repo')).toBe(true); + expect(isAbsoluteOrHomePath('C:\\Users\\Alice\\Repo')).toBe(true); + expect(isAbsoluteOrHomePath('\\\\server\\share\\Repo')).toBe(true); + expect(isAbsoluteOrHomePath('~/Repo')).toBe(true); + expect(isAbsoluteOrHomePath('src/app.ts')).toBe(false); + }); });