Compare commits

..
Author SHA1 Message Date
Tabish Bidiwale a86f401be0 chore: add changeset for release 2025-10-11 02:29:33 +11:00
Tabish Bidiwale 8c65b47abe Fix cross-platform joinPath behavior (#145) 2025-10-11 01:46:32 +11:00
Tabish Bidiwale 9c9e57daa1 Ensure slash command paths resolve on Windows platforms (#144)
* Ensure slash command paths work on Windows

* Add Linux home path coverage for joinPath
2025-10-11 00:27:16 +11:00
github-actions[bot]andTabish Bidiwale c7ca76cb4f chore(release): version packages (#138)
* Version Packages

* chore: trigger CI

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tabish Bidiwale <tabishbidiwale@gmail.com>
2025-10-09 17:48:59 +11:00
06bd3999bf chore(release): version packages (#137)
* Version Packages

* empty

* RUN CI

* trigger CI

* empty

* trigger CI

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tabish Bidiwale <tabishbidiwale@gmail.com>
Co-authored-by: Tabish Bidiwale <30385142+TabishB@users.noreply.github.com>
2025-10-09 17:38:55 +11:00
Tabish Bidiwale 821097079a chore: add changeset for Windows OpenSpec fix (#136)
Add changeset for patch release to fix OpenSpec not working on Windows
when Codex integration is selected. Includes cross-platform path handling
and normalization fixes.
2025-10-09 17:33:16 +11:00
Tabish Bidiwale 42e3118b0c fix: normalize paths for cross-platform consistency in logging (#135)
- Use POSIX-style forward slashes in FILE_PATHS for consistent logging
- Normalize backslashes to forward slashes in update command output
- Improves Windows compatibility and log readability
2025-10-09 17:20:33 +11:00
Tabish Bidiwale a785c2a99a fix: use path.join for cross-platform compatibility in Codex FILE_PATHS (#134)
Fixes #132

The FILE_PATHS constant was using hardcoded forward slashes, which caused
path.basename() to fail on Windows. On Windows, path.basename() expects
backslashes as path separators, so it would return the entire string
instead of just the filename.

This broke Codex detection on Windows during init/update because the
resolveAbsolutePath() method would construct incorrect paths, causing
file existence checks to fail.

Changed FILE_PATHS to use path.join() which automatically uses the
correct platform-specific path separators (backslashes on Windows,
forward slashes on Unix).
2025-10-09 16:51:23 +11:00
github-actions[bot]andTabish Bidiwale af513191eb chore(release): version packages (#131)
* Version Packages

* empty

* RUN CI

* trigger CI

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tabish Bidiwale <tabishbidiwale@gmail.com>
2025-10-09 02:52:47 +11:00
Tabish Bidiwale efbbf3b9f1 chore: add changeset for new release (#130)
Add changeset to release support for Codex and GitHub Copilot slash commands with YAML frontmatter and $ARGUMENTS.
2025-10-09 02:43:58 +11:00
9 changed files with 111 additions and 18 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": minor
---
Prepare a release for the latest GitHub Copilot slash command support and prompt scaffolding improvements.
-5
View File
@@ -1,5 +0,0 @@
---
"@fission-ai/openspec": minor
---
Add support for Codex and GitHub Copilot slash commands with YAML frontmatter and $ARGUMENTS
+12
View File
@@ -1,5 +1,17 @@
# @fission-ai/openspec
## 0.9.1
### Patch Changes
- 8210970: Fix OpenSpec not working on Windows when Codex integration is selected. This release includes fixes for cross-platform path handling and normalization to ensure OpenSpec works correctly on Windows systems.
## 0.9.0
### Minor Changes
- efbbf3b: Add support for Codex and GitHub Copilot slash commands with YAML frontmatter and $ARGUMENTS
## Unreleased
### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@fission-ai/openspec",
"version": "0.8.1",
"version": "0.9.1",
"description": "AI-native system for spec-driven development",
"keywords": [
"openspec",
+3 -4
View File
@@ -1,4 +1,3 @@
import path from 'path';
import { FileSystemUtils } from '../../../utils/file-system.js';
import { TemplateManager, SlashCommandId } from '../../templates/index.js';
import { OPENSPEC_MARKERS } from '../../config.js';
@@ -28,7 +27,7 @@ export abstract class SlashCommandConfigurator {
for (const target of this.getTargets()) {
const body = TemplateManager.getSlashCommandBody(target.id).trim();
const filePath = path.join(projectPath, target.path);
const filePath = FileSystemUtils.joinPath(projectPath, target.path);
if (await FileSystemUtils.fileExists(filePath)) {
await this.updateBody(filePath, body);
@@ -53,7 +52,7 @@ export abstract class SlashCommandConfigurator {
const updated: string[] = [];
for (const target of this.getTargets()) {
const filePath = path.join(projectPath, target.path);
const filePath = FileSystemUtils.joinPath(projectPath, target.path);
if (await FileSystemUtils.fileExists(filePath)) {
const body = TemplateManager.getSlashCommandBody(target.id).trim();
await this.updateBody(filePath, body);
@@ -71,7 +70,7 @@ export abstract class SlashCommandConfigurator {
// to redirect to tool-specific locations (e.g., global directories).
resolveAbsolutePath(projectPath: string, id: SlashCommandId): string {
const rel = this.getRelativePath(id);
return path.join(projectPath, rel);
return FileSystemUtils.joinPath(projectPath, rel);
}
protected async updateBody(filePath: string, body: string): Promise<void> {
+12 -5
View File
@@ -5,6 +5,7 @@ import { SlashCommandId, TemplateManager } from "../../templates/index.js";
import { FileSystemUtils } from "../../../utils/file-system.js";
import { OPENSPEC_MARKERS } from "../../config.js";
// Use POSIX-style paths for consistent logging across platforms.
const FILE_PATHS: Record<SlashCommandId, string> = {
proposal: ".codex/prompts/openspec-proposal.md",
apply: ".codex/prompts/openspec-apply.md",
@@ -48,8 +49,8 @@ $ARGUMENTS`,
private getGlobalPromptsDir(): string {
const home = (process.env.CODEX_HOME && process.env.CODEX_HOME.trim())
? process.env.CODEX_HOME.trim()
: path.join(os.homedir(), ".codex");
return path.join(home, "prompts");
: FileSystemUtils.joinPath(os.homedir(), ".codex");
return FileSystemUtils.joinPath(home, "prompts");
}
// Codex discovers prompts globally. Generate directly in the global directory
@@ -59,7 +60,10 @@ $ARGUMENTS`,
for (const target of this.getTargets()) {
const body = TemplateManager.getSlashCommandBody(target.id).trim();
const promptsDir = this.getGlobalPromptsDir();
const filePath = path.join(promptsDir, path.basename(target.path));
const filePath = FileSystemUtils.joinPath(
promptsDir,
path.basename(target.path)
);
await FileSystemUtils.createDirectory(path.dirname(filePath));
@@ -82,7 +86,10 @@ $ARGUMENTS`,
const updated: string[] = [];
for (const target of this.getTargets()) {
const promptsDir = this.getGlobalPromptsDir();
const filePath = path.join(promptsDir, path.basename(target.path));
const filePath = FileSystemUtils.joinPath(
promptsDir,
path.basename(target.path)
);
if (await FileSystemUtils.fileExists(filePath)) {
const body = TemplateManager.getSlashCommandBody(target.id).trim();
await this.updateFullFile(filePath, target.id, body);
@@ -114,6 +121,6 @@ $ARGUMENTS`,
resolveAbsolutePath(_projectPath: string, id: SlashCommandId): string {
const promptsDir = this.getGlobalPromptsDir();
const fileName = path.basename(FILE_PATHS[id]);
return path.join(promptsDir, fileName);
return FileSystemUtils.joinPath(promptsDir, fileName);
}
}
+3 -3
View File
@@ -106,9 +106,9 @@ export class UpdateCommand {
}
if (updatedSlashFiles.length > 0) {
summaryParts.push(
`Updated slash commands: ${updatedSlashFiles.join(', ')}`
);
// Normalize to forward slashes for cross-platform log consistency
const normalized = updatedSlashFiles.map((p) => p.replace(/\\/g, '/'));
summaryParts.push(`Updated slash commands: ${normalized.join(', ')}`);
}
const failedItems = [
+27
View File
@@ -42,6 +42,33 @@ function findMarkerIndex(
}
export class FileSystemUtils {
private static isWindowsBasePath(basePath: string): boolean {
return /^[A-Za-z]:[\\/]/.test(basePath) || basePath.startsWith('\\');
}
private static normalizeSegments(segments: string[]): string[] {
return segments
.flatMap((segment) => segment.split(/[\\/]+/u))
.filter((part) => part.length > 0);
}
static joinPath(basePath: string, ...segments: string[]): string {
const normalizedSegments = this.normalizeSegments(segments);
if (this.isWindowsBasePath(basePath)) {
const normalizedBasePath = path.win32.normalize(basePath);
return normalizedSegments.length
? path.win32.join(normalizedBasePath, ...normalizedSegments)
: normalizedBasePath;
}
const posixBasePath = basePath.replace(/\\/g, '/');
return normalizedSegments.length
? path.posix.join(posixBasePath, ...normalizedSegments)
: path.posix.normalize(posixBasePath);
}
static async createDirectory(dirPath: string): Promise<void> {
await fs.mkdir(dirPath, { recursive: true });
}
+48
View File
@@ -160,4 +160,52 @@ describe('FileSystemUtils', () => {
expect(hasPermission).toBe(true);
});
});
describe('joinPath', () => {
it('should join POSIX-style paths', () => {
const result = FileSystemUtils.joinPath(
'/tmp/project',
'.claude/commands/openspec/proposal.md'
);
expect(result).toBe('/tmp/project/.claude/commands/openspec/proposal.md');
});
it('should join Linux home directory paths', () => {
const result = FileSystemUtils.joinPath(
'/home/dev/workspace/openspec',
'.cursor/commands/install.md'
);
expect(result).toBe('/home/dev/workspace/openspec/.cursor/commands/install.md');
});
it('should join Windows drive-letter paths with backslashes', () => {
const result = FileSystemUtils.joinPath(
'C:\\Users\\dev\\project',
'.claude/commands/openspec/proposal.md'
);
expect(result).toBe(
'C:\\Users\\dev\\project\\.claude\\commands\\openspec\\proposal.md'
);
});
it('should join Windows paths that use forward slashes', () => {
const result = FileSystemUtils.joinPath(
'D:/workspace/app',
'.cursor/commands/openspec-apply.md'
);
expect(result).toBe(
'D:\\workspace\\app\\.cursor\\commands\\openspec-apply.md'
);
});
it('should join UNC-style Windows paths', () => {
const result = FileSystemUtils.joinPath(
'\\server\\share\\repo',
'.windsurf/workflows/openspec-archive.md'
);
expect(result).toBe(
'\\server\\share\\repo\\.windsurf\\workflows\\openspec-archive.md'
);
});
});
});