From 9238589a554b3acc1b645f2bcac7f35fa518818b Mon Sep 17 00:00:00 2001 From: Rafael Oliveira Date: Tue, 30 Jun 2026 15:34:35 -0300 Subject: [PATCH] fix(paths): normalize displayed paths to POSIX on Windows Splash tilde paths, listFiles glob results, and workflowStatePath responses used native backslashes on win32, breaking tests and MCP consumers that expect forward slashes. Add toPosixContextPath and apply at API boundaries. --- .../application/actions/workflowActionService.ts | 10 +++++----- src/harness/application/context/contextTools.ts | 4 ++-- src/harness/application/workflow/plansService.ts | 4 ++-- src/shared/fs/pathHelpers.ts | 7 +++++++ src/utils/splashScreen.ts | 4 ++-- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/harness/application/actions/workflowActionService.ts b/src/harness/application/actions/workflowActionService.ts index 801317e8..b654b2a2 100644 --- a/src/harness/application/actions/workflowActionService.ts +++ b/src/harness/application/actions/workflowActionService.ts @@ -12,7 +12,7 @@ import { } from '../workflow'; import { HarnessPolicyBlockedError } from '../policies/policyService'; -import { resolveRuntimeLayout } from '../../../shared/fs/pathHelpers'; +import { resolveRuntimeLayout, toPosixContextPath } from '../../../shared/fs/pathHelpers'; import type { HarnessWorkflowGuideInput } from '../workflow/workflowGuideTypes'; export interface HarnessWorkflowInitInput { @@ -62,7 +62,7 @@ export class HarnessWorkflowActionService { archivePrevious: params.archive_previous, }); - const workflowStatePath = resolveRuntimeLayout(contextPath).prevcFile; + const workflowStatePath = toPosixContextPath(resolveRuntimeLayout(contextPath).prevcFile); const settings = await service.getSettings(); const scale = getScaleName(status.project.scale as ProjectScale); const isAutonomous = settings.autonomous_mode; @@ -131,13 +131,13 @@ export class HarnessWorkflowActionService { error: 'No workflow found. Initialize a workflow first.', suggestion: 'Use workflow-init({ name: "feature-name" }) to start.', note: 'Workflows enable structured PREVC phases. Skip for trivial changes.', - workflowStatePath: resolveRuntimeLayout(contextPath).prevcFile, + workflowStatePath: toPosixContextPath(resolveRuntimeLayout(contextPath).prevcFile), }; } const summary = await service.getSummary(); const status = await service.getStatus(); - const workflowStatePath = resolveRuntimeLayout(contextPath).prevcFile; + const workflowStatePath = toPosixContextPath(resolveRuntimeLayout(contextPath).prevcFile); const orchestration = await service.getPhaseOrchestration(summary.currentPhase); const harness = await service.getHarnessStatus(); @@ -170,7 +170,7 @@ export class HarnessWorkflowActionService { success: false, error: 'No workflow found. Initialize a workflow first.', suggestion: 'Use workflow-init({ name: "feature-name" }) to start.', - workflowStatePath: resolveRuntimeLayout(contextPath).prevcFile, + workflowStatePath: toPosixContextPath(resolveRuntimeLayout(contextPath).prevcFile), }; } diff --git a/src/harness/application/context/contextTools.ts b/src/harness/application/context/contextTools.ts index 03945c34..26e946ef 100644 --- a/src/harness/application/context/contextTools.ts +++ b/src/harness/application/context/contextTools.ts @@ -348,11 +348,11 @@ export const listFilesTool = createInternalTool< async (input) => { const { pattern, cwd, ignore } = input; try { - const files = await glob(pattern, { + const files = (await glob(pattern, { cwd: cwd || process.cwd(), ignore: ignore || ['node_modules/**', '.git/**', 'dist/**'], absolute: false - }); + })).map((file) => file.split(path.sep).join('/')); return { success: true, files, diff --git a/src/harness/application/workflow/plansService.ts b/src/harness/application/workflow/plansService.ts index dea0614a..714c94bd 100644 --- a/src/harness/application/workflow/plansService.ts +++ b/src/harness/application/workflow/plansService.ts @@ -15,7 +15,7 @@ import { } from '../../domain/workflow/plans'; import { PrevcStatusManager } from '../../domain/workflow/status/statusManager'; import { GitService } from '../../../utils/gitService'; -import { resolveRuntimeLayoutFromRepo } from '../../../shared/fs/pathHelpers'; +import { resolveRuntimeLayoutFromRepo, toPosixContextPath } from '../../../shared/fs/pathHelpers'; import type { PrevcPhase } from '../../domain/workflow/types'; export interface HarnessPlansServiceOptions { @@ -87,7 +87,7 @@ export class HarnessPlansService { canAdvanceToReview = gateResult.gates.plan_required.passed; } - const workflowStatePath = resolveRuntimeLayoutFromRepo(this.repoPath).prevcFile; + const workflowStatePath = toPosixContextPath(resolveRuntimeLayoutFromRepo(this.repoPath).prevcFile); const enhancementPrompt = workflowActive ? `PLAN LINKED TO ACTIVE WORKFLOW diff --git a/src/shared/fs/pathHelpers.ts b/src/shared/fs/pathHelpers.ts index ba5f8b3d..a8694956 100644 --- a/src/shared/fs/pathHelpers.ts +++ b/src/shared/fs/pathHelpers.ts @@ -245,6 +245,13 @@ export async function isFile(targetPath: string): Promise { } } +/** + * Normalize an absolute path for cross-platform API responses (forward slashes). + */ +export function toPosixContextPath(absolutePath: string): string { + return absolutePath.split(path.sep).join('/'); +} + /** * Normalize path separators to forward slashes */ diff --git a/src/utils/splashScreen.ts b/src/utils/splashScreen.ts index 53911c8f..170654fe 100644 --- a/src/utils/splashScreen.ts +++ b/src/utils/splashScreen.ts @@ -60,8 +60,8 @@ export function formatSplashDirectory(directory: string, maxLength = 48): string const withTilde = resolved === homeDirectory ? '~' : resolved.startsWith(`${homeDirectory}${path.sep}`) - ? `~${path.sep}${path.relative(homeDirectory, resolved)}` - : resolved; + ? `~/${path.relative(homeDirectory, resolved).split(path.sep).join('/')}` + : resolved.split(path.sep).join('/'); return truncateMiddle(withTilde, maxLength); }