@fission-ai/openspec 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/archive.js +40 -2
- package/dist/core/command-generation/adapters/codex.d.ts +4 -1
- package/dist/core/command-generation/adapters/codex.js +14 -2
- package/dist/core/command-generation/adapters/windsurf.d.ts +1 -1
- package/dist/core/command-generation/adapters/windsurf.js +2 -2
- package/dist/core/command-generation/types.d.ts +4 -3
- package/dist/core/config.js +1 -0
- package/dist/core/init.js +1 -1
- package/dist/core/templates/skill-templates.js +2 -2
- package/dist/core/update.js +2 -2
- package/package.json +1 -1
package/dist/core/archive.js
CHANGED
|
@@ -4,6 +4,44 @@ import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progre
|
|
|
4
4
|
import { Validator } from './validation/validator.js';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { findSpecUpdates, buildUpdatedSpec, writeUpdatedSpec, } from './specs-apply.js';
|
|
7
|
+
/**
|
|
8
|
+
* Recursively copy a directory. Used when fs.rename fails (e.g. EPERM on Windows).
|
|
9
|
+
*/
|
|
10
|
+
async function copyDirRecursive(src, dest) {
|
|
11
|
+
await fs.mkdir(dest, { recursive: true });
|
|
12
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const srcPath = path.join(src, entry.name);
|
|
15
|
+
const destPath = path.join(dest, entry.name);
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
await copyDirRecursive(srcPath, destPath);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
await fs.copyFile(srcPath, destPath);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Move a directory from src to dest. On Windows, fs.rename() often fails with
|
|
26
|
+
* EPERM when the directory is non-empty or another process has it open (IDE,
|
|
27
|
+
* file watcher, antivirus). Fall back to copy-then-remove when rename fails
|
|
28
|
+
* with EPERM or EXDEV.
|
|
29
|
+
*/
|
|
30
|
+
async function moveDirectory(src, dest) {
|
|
31
|
+
try {
|
|
32
|
+
await fs.rename(src, dest);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
const code = err?.code;
|
|
36
|
+
if (code === 'EPERM' || code === 'EXDEV') {
|
|
37
|
+
await copyDirRecursive(src, dest);
|
|
38
|
+
await fs.rm(src, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
7
45
|
export class ArchiveCommand {
|
|
8
46
|
async execute(changeName, options = {}) {
|
|
9
47
|
const targetPath = '.';
|
|
@@ -225,8 +263,8 @@ export class ArchiveCommand {
|
|
|
225
263
|
}
|
|
226
264
|
// Create archive directory if needed
|
|
227
265
|
await fs.mkdir(archiveDir, { recursive: true });
|
|
228
|
-
// Move change to archive
|
|
229
|
-
await
|
|
266
|
+
// Move change to archive (uses copy+remove on EPERM/EXDEV, e.g. Windows)
|
|
267
|
+
await moveDirectory(changeDir, archivePath);
|
|
230
268
|
console.log(`Change '${changeName}' archived as '${archiveName}'.`);
|
|
231
269
|
}
|
|
232
270
|
async selectChange(changesDir) {
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
* Codex Command Adapter
|
|
3
3
|
*
|
|
4
4
|
* Formats commands for Codex following its frontmatter specification.
|
|
5
|
+
* Codex custom prompts live in the global home directory (~/.codex/prompts/)
|
|
6
|
+
* and are not shared through the repository. The CODEX_HOME env var can
|
|
7
|
+
* override the default ~/.codex location.
|
|
5
8
|
*/
|
|
6
9
|
import type { ToolCommandAdapter } from '../types.js';
|
|
7
10
|
/**
|
|
8
11
|
* Codex adapter for command generation.
|
|
9
|
-
* File path:
|
|
12
|
+
* File path: <CODEX_HOME>/prompts/opsx-<id>.md (absolute, global)
|
|
10
13
|
* Frontmatter: description, argument-hint
|
|
11
14
|
*/
|
|
12
15
|
export declare const codexAdapter: ToolCommandAdapter;
|
|
@@ -2,17 +2,29 @@
|
|
|
2
2
|
* Codex Command Adapter
|
|
3
3
|
*
|
|
4
4
|
* Formats commands for Codex following its frontmatter specification.
|
|
5
|
+
* Codex custom prompts live in the global home directory (~/.codex/prompts/)
|
|
6
|
+
* and are not shared through the repository. The CODEX_HOME env var can
|
|
7
|
+
* override the default ~/.codex location.
|
|
5
8
|
*/
|
|
9
|
+
import os from 'os';
|
|
6
10
|
import path from 'path';
|
|
11
|
+
/**
|
|
12
|
+
* Returns the Codex home directory.
|
|
13
|
+
* Respects the CODEX_HOME env var, defaulting to ~/.codex.
|
|
14
|
+
*/
|
|
15
|
+
function getCodexHome() {
|
|
16
|
+
const envHome = process.env.CODEX_HOME?.trim();
|
|
17
|
+
return path.resolve(envHome ? envHome : path.join(os.homedir(), '.codex'));
|
|
18
|
+
}
|
|
7
19
|
/**
|
|
8
20
|
* Codex adapter for command generation.
|
|
9
|
-
* File path:
|
|
21
|
+
* File path: <CODEX_HOME>/prompts/opsx-<id>.md (absolute, global)
|
|
10
22
|
* Frontmatter: description, argument-hint
|
|
11
23
|
*/
|
|
12
24
|
export const codexAdapter = {
|
|
13
25
|
toolId: 'codex',
|
|
14
26
|
getFilePath(commandId) {
|
|
15
|
-
return path.join(
|
|
27
|
+
return path.join(getCodexHome(), 'prompts', `opsx-${commandId}.md`);
|
|
16
28
|
},
|
|
17
29
|
formatFile(content) {
|
|
18
30
|
return `---
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { ToolCommandAdapter } from '../types.js';
|
|
8
8
|
/**
|
|
9
9
|
* Windsurf adapter for command generation.
|
|
10
|
-
* File path: .windsurf/
|
|
10
|
+
* File path: .windsurf/workflows/opsx-<id>.md
|
|
11
11
|
* Frontmatter: name, description, category, tags
|
|
12
12
|
*/
|
|
13
13
|
export declare const windsurfAdapter: ToolCommandAdapter;
|
|
@@ -28,13 +28,13 @@ function formatTagsArray(tags) {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Windsurf adapter for command generation.
|
|
31
|
-
* File path: .windsurf/
|
|
31
|
+
* File path: .windsurf/workflows/opsx-<id>.md
|
|
32
32
|
* Frontmatter: name, description, category, tags
|
|
33
33
|
*/
|
|
34
34
|
export const windsurfAdapter = {
|
|
35
35
|
toolId: 'windsurf',
|
|
36
36
|
getFilePath(commandId) {
|
|
37
|
-
return path.join('.windsurf', '
|
|
37
|
+
return path.join('.windsurf', 'workflows', `opsx-${commandId}.md`);
|
|
38
38
|
},
|
|
39
39
|
formatFile(content) {
|
|
40
40
|
return `---
|
|
@@ -31,9 +31,10 @@ export interface ToolCommandAdapter {
|
|
|
31
31
|
/** Tool identifier matching AIToolOption.value (e.g., 'claude', 'cursor') */
|
|
32
32
|
toolId: string;
|
|
33
33
|
/**
|
|
34
|
-
* Returns the
|
|
34
|
+
* Returns the file path for a command.
|
|
35
35
|
* @param commandId - The command identifier (e.g., 'explore')
|
|
36
|
-
* @returns
|
|
36
|
+
* @returns Path from project root (e.g., '.claude/commands/opsx/explore.md').
|
|
37
|
+
* May be absolute for tools with global-scoped prompts (e.g., Codex).
|
|
37
38
|
*/
|
|
38
39
|
getFilePath(commandId: string): string;
|
|
39
40
|
/**
|
|
@@ -47,7 +48,7 @@ export interface ToolCommandAdapter {
|
|
|
47
48
|
* Result of generating a command file.
|
|
48
49
|
*/
|
|
49
50
|
export interface GeneratedCommand {
|
|
50
|
-
/**
|
|
51
|
+
/** File path from project root, or absolute for global-scoped tools */
|
|
51
52
|
path: string;
|
|
52
53
|
/** Complete file content (frontmatter + body) */
|
|
53
54
|
fileContent: string;
|
package/dist/core/config.js
CHANGED
|
@@ -24,6 +24,7 @@ export const AI_TOOLS = [
|
|
|
24
24
|
{ name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder' },
|
|
25
25
|
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code', skillsDir: '.qwen' },
|
|
26
26
|
{ name: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode', skillsDir: '.roo' },
|
|
27
|
+
{ name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae' },
|
|
27
28
|
{ name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf', skillsDir: '.windsurf' },
|
|
28
29
|
{ name: 'AGENTS.md (works with Amp, VS Code, …)', value: 'agents', available: false, successLabel: 'your AGENTS.md-compatible assistant' }
|
|
29
30
|
];
|
package/dist/core/init.js
CHANGED
|
@@ -310,7 +310,7 @@ export class InitCommand {
|
|
|
310
310
|
if (adapter) {
|
|
311
311
|
const generatedCommands = generateCommands(commandContents, adapter);
|
|
312
312
|
for (const cmd of generatedCommands) {
|
|
313
|
-
const commandFile = path.join(projectPath, cmd.path);
|
|
313
|
+
const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(projectPath, cmd.path);
|
|
314
314
|
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
315
315
|
}
|
|
316
316
|
}
|
|
@@ -1730,7 +1730,7 @@ export function getOpsxContinueCommandTemplate() {
|
|
|
1730
1730
|
**If all artifacts are complete (\`isComplete: true\`)**:
|
|
1731
1731
|
- Congratulate the user
|
|
1732
1732
|
- Show final status including the schema used
|
|
1733
|
-
- Suggest: "All artifacts created! You can now implement this change or archive it
|
|
1733
|
+
- Suggest: "All artifacts created! You can now implement this change with \`/opsx:apply\` or archive it with \`/opsx:archive\`."
|
|
1734
1734
|
- STOP
|
|
1735
1735
|
|
|
1736
1736
|
---
|
|
@@ -1918,7 +1918,7 @@ Working on task 4/7: <task description>
|
|
|
1918
1918
|
- [x] Task 2
|
|
1919
1919
|
...
|
|
1920
1920
|
|
|
1921
|
-
All tasks complete!
|
|
1921
|
+
All tasks complete! You can archive this change with \`/opsx:archive\`.
|
|
1922
1922
|
\`\`\`
|
|
1923
1923
|
|
|
1924
1924
|
**Output On Pause (Issue Encountered)**
|
package/dist/core/update.js
CHANGED
|
@@ -81,7 +81,7 @@ export class UpdateCommand {
|
|
|
81
81
|
if (adapter) {
|
|
82
82
|
const generatedCommands = generateCommands(commandContents, adapter);
|
|
83
83
|
for (const cmd of generatedCommands) {
|
|
84
|
-
const commandFile = path.join(resolvedProjectPath, cmd.path);
|
|
84
|
+
const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(resolvedProjectPath, cmd.path);
|
|
85
85
|
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
@@ -285,7 +285,7 @@ export class UpdateCommand {
|
|
|
285
285
|
if (adapter) {
|
|
286
286
|
const generatedCommands = generateCommands(commandContents, adapter);
|
|
287
287
|
for (const cmd of generatedCommands) {
|
|
288
|
-
const commandFile = path.join(projectPath, cmd.path);
|
|
288
|
+
const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(projectPath, cmd.path);
|
|
289
289
|
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
290
290
|
}
|
|
291
291
|
}
|