Compare commits

..
Author SHA1 Message Date
Tabish Bidiwale b1a7784586 chore: add changeset for Windows OpenSpec fix
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:29:39 +11:00
12 changed files with 16 additions and 150 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"@fission-ai/openspec": minor
---
Improve init wizard Enter key behavior to allow proceeding through prompts more naturally
+5
View File
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---
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.
-12
View File
@@ -1,17 +1,5 @@
# @fission-ai/openspec
## 0.9.2
### Patch Changes
- 2ae0484: Fix cross-platform path handling issues. This release includes fixes for joinPath behavior and slash command path resolution to ensure OpenSpec works correctly across all platforms.
## 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
@@ -1,14 +0,0 @@
## Why
- Users frequently scroll to a tool and press Enter without toggling it, resulting in no configuration changes.
- The current workflow deviates from common CLI expectations where Enter confirms the highlighted item.
- Aligning behavior with user expectations reduces friction during onboarding.
## What Changes
- Update the init wizard so pressing Enter on a highlighted tool selects it before moving to the review step.
- Adjust interactive instructions to clarify Enter selects the current tool and Space still toggles selections.
- Refresh specs to capture the clarified behavior for the interactive menu.
## Impact
- Users who press Enter without toggling now configure the highlighted tool instead of exiting with no selections.
- Spacebar multi-select support remains unchanged for power users.
- Documentation better reflects how the wizard behaves.
@@ -1,10 +0,0 @@
## MODIFIED Requirements
### Requirement: Interactive Mode
The command SHALL provide an interactive menu for AI tool selection with clear navigation instructions.
#### Scenario: Displaying interactive menu
- **WHEN** run in fresh or extend mode
- **THEN** present a looping select menu that lets users toggle tools with Space and review selections with Enter
- **AND** when Enter is pressed on a highlighted selectable tool that is not already selected, automatically add it to the selection before moving to review so the highlighted tool is configured
- **AND** label already configured tools with "(already configured)" while keeping disabled options marked "coming soon"
- **AND** change the prompt copy in extend mode to "Which AI tools would you like to add or refresh?"
- **AND** display inline instructions clarifying that Space toggles tools and Enter selects the highlighted tool before reviewing selections
@@ -1,8 +0,0 @@
## 1. Implementation
- [x] Update the tool selection wizard to auto-select the highlighted tool when Enter is pressed without prior toggles.
- [x] Refresh inline instructions copy so Enter behavior is clear.
- [x] Adjust or add tests if needed to cover the new selection flow.
## 2. Validation
- [x] Run `pnpm run build`.
- [x] Run `pnpm test` (or targeted suite) if applicable.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@fission-ai/openspec",
"version": "0.9.2",
"version": "0.9.0",
"description": "AI-native system for spec-driven development",
"keywords": [
"openspec",
+4 -3
View File
@@ -1,3 +1,4 @@
import path from 'path';
import { FileSystemUtils } from '../../../utils/file-system.js';
import { TemplateManager, SlashCommandId } from '../../templates/index.js';
import { OPENSPEC_MARKERS } from '../../config.js';
@@ -27,7 +28,7 @@ export abstract class SlashCommandConfigurator {
for (const target of this.getTargets()) {
const body = TemplateManager.getSlashCommandBody(target.id).trim();
const filePath = FileSystemUtils.joinPath(projectPath, target.path);
const filePath = path.join(projectPath, target.path);
if (await FileSystemUtils.fileExists(filePath)) {
await this.updateBody(filePath, body);
@@ -52,7 +53,7 @@ export abstract class SlashCommandConfigurator {
const updated: string[] = [];
for (const target of this.getTargets()) {
const filePath = FileSystemUtils.joinPath(projectPath, target.path);
const filePath = path.join(projectPath, target.path);
if (await FileSystemUtils.fileExists(filePath)) {
const body = TemplateManager.getSlashCommandBody(target.id).trim();
await this.updateBody(filePath, body);
@@ -70,7 +71,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 FileSystemUtils.joinPath(projectPath, rel);
return path.join(projectPath, rel);
}
protected async updateBody(filePath: string, body: string): Promise<void> {
+5 -11
View File
@@ -49,8 +49,8 @@ $ARGUMENTS`,
private getGlobalPromptsDir(): string {
const home = (process.env.CODEX_HOME && process.env.CODEX_HOME.trim())
? process.env.CODEX_HOME.trim()
: FileSystemUtils.joinPath(os.homedir(), ".codex");
return FileSystemUtils.joinPath(home, "prompts");
: path.join(os.homedir(), ".codex");
return path.join(home, "prompts");
}
// Codex discovers prompts globally. Generate directly in the global directory
@@ -60,10 +60,7 @@ $ARGUMENTS`,
for (const target of this.getTargets()) {
const body = TemplateManager.getSlashCommandBody(target.id).trim();
const promptsDir = this.getGlobalPromptsDir();
const filePath = FileSystemUtils.joinPath(
promptsDir,
path.basename(target.path)
);
const filePath = path.join(promptsDir, path.basename(target.path));
await FileSystemUtils.createDirectory(path.dirname(filePath));
@@ -86,10 +83,7 @@ $ARGUMENTS`,
const updated: string[] = [];
for (const target of this.getTargets()) {
const promptsDir = this.getGlobalPromptsDir();
const filePath = FileSystemUtils.joinPath(
promptsDir,
path.basename(target.path)
);
const filePath = path.join(promptsDir, path.basename(target.path));
if (await FileSystemUtils.fileExists(filePath)) {
const body = TemplateManager.getSlashCommandBody(target.id).trim();
await this.updateFullFile(filePath, target.id, body);
@@ -121,6 +115,6 @@ $ARGUMENTS`,
resolveAbsolutePath(_projectPath: string, id: SlashCommandId): string {
const promptsDir = this.getGlobalPromptsDir();
const fileName = path.basename(FILE_PATHS[id]);
return FileSystemUtils.joinPath(promptsDir, fileName);
return path.join(promptsDir, fileName);
}
}
+1 -11
View File
@@ -220,16 +220,6 @@ const toolSelectionWizard = createPrompt<string[], ToolWizardConfig>(
}
if (isEnterKey(key)) {
const current = config.choices[cursor];
if (
current &&
current.selectable &&
!selectedSet.has(current.value)
) {
const next = new Set(selected);
next.add(current.value);
updateSelected(next);
}
setStep('review');
setError(null);
return;
@@ -308,7 +298,7 @@ const toolSelectionWizard = createPrompt<string[], ToolWizardConfig>(
lines.push(PALETTE.white(config.baseMessage));
lines.push(
PALETTE.midGray(
'Use ↑/↓ to move · Space to toggle · Enter selects highlighted tool and reviews.'
'Use ↑/↓ to move · Space to toggle · Enter to review selections.'
)
);
lines.push('');
-27
View File
@@ -42,33 +42,6 @@ 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,52 +160,4 @@ 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'
);
});
});
});