diff --git a/src/core/artifact-graph/state.ts b/src/core/artifact-graph/state.ts index 449bb723..55c6fd08 100644 --- a/src/core/artifact-graph/state.ts +++ b/src/core/artifact-graph/state.ts @@ -3,6 +3,7 @@ import * as path from 'node:path'; import fg from 'fast-glob'; import type { CompletedSet } from './types.js'; import type { ArtifactGraph } from './graph.js'; +import { FileSystemUtils } from '../../utils/file-system.js'; /** * Detects which artifacts are completed by checking file existence in the change directory. @@ -54,8 +55,10 @@ function isGlobPattern(pattern: string): boolean { /** * Checks if a glob pattern has any matches. + * Normalizes Windows backslashes to forward slashes for cross-platform glob compatibility. */ function hasGlobMatches(pattern: string): boolean { - const matches = fg.sync(pattern, { onlyFiles: true }); + const normalizedPattern = FileSystemUtils.toPosixPath(pattern); + const matches = fg.sync(normalizedPattern, { onlyFiles: true }); return matches.length > 0; } diff --git a/src/core/converters/json-converter.ts b/src/core/converters/json-converter.ts index 162b4e7b..b8468c2a 100644 --- a/src/core/converters/json-converter.ts +++ b/src/core/converters/json-converter.ts @@ -3,6 +3,7 @@ import path from 'path'; import { MarkdownParser } from '../parsers/markdown-parser.js'; import { ChangeParser } from '../parsers/change-parser.js'; import { Spec, Change } from '../schemas/index.js'; +import { FileSystemUtils } from '../../utils/file-system.js'; export class JsonConverter { convertSpecToJson(filePath: string): string { @@ -43,7 +44,7 @@ export class JsonConverter { } private extractNameFromPath(filePath: string): string { - const normalizedPath = filePath.replaceAll('\\', '/'); + const normalizedPath = FileSystemUtils.toPosixPath(filePath); const parts = normalizedPath.split('/'); for (let i = parts.length - 1; i >= 0; i--) { diff --git a/src/core/update.ts b/src/core/update.ts index 6d75898e..41fd7720 100644 --- a/src/core/update.ts +++ b/src/core/update.ts @@ -107,7 +107,7 @@ export class UpdateCommand { if (updatedSlashFiles.length > 0) { // Normalize to forward slashes for cross-platform log consistency - const normalized = updatedSlashFiles.map((p) => p.replace(/\\/g, '/')); + const normalized = updatedSlashFiles.map((p) => FileSystemUtils.toPosixPath(p)); summaryParts.push(`Updated slash commands: ${normalized.join(', ')}`); } diff --git a/src/core/validation/validator.ts b/src/core/validation/validator.ts index c15b70c2..e6928cbd 100644 --- a/src/core/validation/validator.ts +++ b/src/core/validation/validator.ts @@ -5,12 +5,13 @@ import { SpecSchema, ChangeSchema, Spec, Change } from '../schemas/index.js'; import { MarkdownParser } from '../parsers/markdown-parser.js'; import { ChangeParser } from '../parsers/change-parser.js'; import { ValidationReport, ValidationIssue, ValidationLevel } from './types.js'; -import { +import { MIN_PURPOSE_LENGTH, MAX_REQUIREMENT_TEXT_LENGTH, - VALIDATION_MESSAGES + VALIDATION_MESSAGES } from './constants.js'; import { parseDeltaSpec, normalizeRequirementName } from '../parsers/requirement-blocks.js'; +import { FileSystemUtils } from '../../utils/file-system.js'; export class Validator { private strictMode: boolean; @@ -359,7 +360,7 @@ export class Validator { } private extractNameFromPath(filePath: string): string { - const normalizedPath = filePath.replaceAll('\\', '/'); + const normalizedPath = FileSystemUtils.toPosixPath(filePath); const parts = normalizedPath.split('/'); // Look for the directory name after 'specs' or 'changes' diff --git a/src/utils/file-system.ts b/src/utils/file-system.ts index 364b5cda..bce759d5 100644 --- a/src/utils/file-system.ts +++ b/src/utils/file-system.ts @@ -42,6 +42,14 @@ function findMarkerIndex( } export class FileSystemUtils { + /** + * Converts a path to use forward slashes (POSIX style). + * Essential for cross-platform compatibility with glob libraries like fast-glob. + */ + static toPosixPath(p: string): string { + return p.replace(/\\/g, '/'); + } + private static isWindowsBasePath(basePath: string): boolean { return /^[A-Za-z]:[\\/]/.test(basePath) || basePath.startsWith('\\'); }