@fission-ai/openspec 0.15.0 → 0.17.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/README.md +21 -12
- package/dist/cli/index.js +67 -2
- package/dist/commands/change.js +4 -3
- package/dist/commands/completion.d.ts +72 -0
- package/dist/commands/completion.js +221 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +198 -0
- package/dist/commands/show.js +3 -2
- package/dist/commands/spec.js +4 -3
- package/dist/commands/validate.js +21 -2
- package/dist/core/archive.js +4 -1
- package/dist/core/completions/command-registry.d.ts +7 -0
- package/dist/core/completions/command-registry.js +362 -0
- package/dist/core/completions/completion-provider.d.ts +60 -0
- package/dist/core/completions/completion-provider.js +102 -0
- package/dist/core/completions/factory.d.ts +51 -0
- package/dist/core/completions/factory.js +57 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +58 -0
- package/dist/core/completions/generators/zsh-generator.js +319 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +136 -0
- package/dist/core/completions/installers/zsh-installer.js +449 -0
- package/dist/core/completions/types.d.ts +78 -0
- package/dist/core/completions/types.js +2 -0
- package/dist/core/config-schema.d.ts +76 -0
- package/dist/core/config-schema.js +200 -0
- package/dist/core/config.js +8 -6
- package/dist/core/configurators/iflow.d.ts +8 -0
- package/dist/core/configurators/iflow.js +15 -0
- package/dist/core/configurators/registry.js +3 -0
- package/dist/core/configurators/slash/antigravity.d.ts +9 -0
- package/dist/core/configurators/slash/antigravity.js +23 -0
- package/dist/core/configurators/slash/gemini.d.ts +3 -6
- package/dist/core/configurators/slash/gemini.js +4 -50
- package/dist/core/configurators/slash/iflow.d.ts +9 -0
- package/dist/core/configurators/slash/iflow.js +37 -0
- package/dist/core/configurators/slash/opencode.js +0 -3
- package/dist/core/configurators/slash/qwen.d.ts +3 -8
- package/dist/core/configurators/slash/qwen.js +11 -36
- package/dist/core/configurators/slash/registry.js +6 -0
- package/dist/core/configurators/slash/toml-base.d.ts +10 -0
- package/dist/core/configurators/slash/toml-base.js +53 -0
- package/dist/core/global-config.d.ts +29 -0
- package/dist/core/global-config.js +87 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/init.js +7 -0
- package/dist/core/templates/slash-command-templates.js +2 -1
- package/dist/utils/file-system.js +19 -3
- package/dist/utils/interactive.d.ts +12 -1
- package/dist/utils/interactive.js +7 -2
- package/dist/utils/item-discovery.d.ts +1 -0
- package/dist/utils/item-discovery.js +23 -0
- package/dist/utils/shell-detection.d.ts +20 -0
- package/dist/utils/shell-detection.js +41 -0
- package/package.json +4 -1
- package/scripts/postinstall.js +147 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for global OpenSpec configuration.
|
|
4
|
+
* Uses passthrough() to preserve unknown fields for forward compatibility.
|
|
5
|
+
*/
|
|
6
|
+
export const GlobalConfigSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
featureFlags: z
|
|
9
|
+
.record(z.string(), z.boolean())
|
|
10
|
+
.optional()
|
|
11
|
+
.default({}),
|
|
12
|
+
})
|
|
13
|
+
.passthrough();
|
|
14
|
+
/**
|
|
15
|
+
* Default configuration values.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_CONFIG = {
|
|
18
|
+
featureFlags: {},
|
|
19
|
+
};
|
|
20
|
+
const KNOWN_TOP_LEVEL_KEYS = new Set(Object.keys(DEFAULT_CONFIG));
|
|
21
|
+
/**
|
|
22
|
+
* Validate a config key path for CLI set operations.
|
|
23
|
+
* Unknown top-level keys are rejected unless explicitly allowed by the caller.
|
|
24
|
+
*/
|
|
25
|
+
export function validateConfigKeyPath(path) {
|
|
26
|
+
const rawKeys = path.split('.');
|
|
27
|
+
if (rawKeys.length === 0 || rawKeys.some((key) => key.trim() === '')) {
|
|
28
|
+
return { valid: false, reason: 'Key path must not be empty' };
|
|
29
|
+
}
|
|
30
|
+
const rootKey = rawKeys[0];
|
|
31
|
+
if (!KNOWN_TOP_LEVEL_KEYS.has(rootKey)) {
|
|
32
|
+
return { valid: false, reason: `Unknown top-level key "${rootKey}"` };
|
|
33
|
+
}
|
|
34
|
+
if (rootKey === 'featureFlags') {
|
|
35
|
+
if (rawKeys.length > 2) {
|
|
36
|
+
return { valid: false, reason: 'featureFlags values are booleans and do not support nested keys' };
|
|
37
|
+
}
|
|
38
|
+
return { valid: true };
|
|
39
|
+
}
|
|
40
|
+
if (rawKeys.length > 1) {
|
|
41
|
+
return { valid: false, reason: `"${rootKey}" does not support nested keys` };
|
|
42
|
+
}
|
|
43
|
+
return { valid: true };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get a nested value from an object using dot notation.
|
|
47
|
+
*
|
|
48
|
+
* @param obj - The object to access
|
|
49
|
+
* @param path - Dot-separated path (e.g., "featureFlags.someFlag")
|
|
50
|
+
* @returns The value at the path, or undefined if not found
|
|
51
|
+
*/
|
|
52
|
+
export function getNestedValue(obj, path) {
|
|
53
|
+
const keys = path.split('.');
|
|
54
|
+
let current = obj;
|
|
55
|
+
for (const key of keys) {
|
|
56
|
+
if (current === null || current === undefined) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
if (typeof current !== 'object') {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
current = current[key];
|
|
63
|
+
}
|
|
64
|
+
return current;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Set a nested value in an object using dot notation.
|
|
68
|
+
* Creates intermediate objects as needed.
|
|
69
|
+
*
|
|
70
|
+
* @param obj - The object to modify (mutated in place)
|
|
71
|
+
* @param path - Dot-separated path (e.g., "featureFlags.someFlag")
|
|
72
|
+
* @param value - The value to set
|
|
73
|
+
*/
|
|
74
|
+
export function setNestedValue(obj, path, value) {
|
|
75
|
+
const keys = path.split('.');
|
|
76
|
+
let current = obj;
|
|
77
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
78
|
+
const key = keys[i];
|
|
79
|
+
if (current[key] === undefined || current[key] === null || typeof current[key] !== 'object') {
|
|
80
|
+
current[key] = {};
|
|
81
|
+
}
|
|
82
|
+
current = current[key];
|
|
83
|
+
}
|
|
84
|
+
const lastKey = keys[keys.length - 1];
|
|
85
|
+
current[lastKey] = value;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Delete a nested value from an object using dot notation.
|
|
89
|
+
*
|
|
90
|
+
* @param obj - The object to modify (mutated in place)
|
|
91
|
+
* @param path - Dot-separated path (e.g., "featureFlags.someFlag")
|
|
92
|
+
* @returns true if the key existed and was deleted, false otherwise
|
|
93
|
+
*/
|
|
94
|
+
export function deleteNestedValue(obj, path) {
|
|
95
|
+
const keys = path.split('.');
|
|
96
|
+
let current = obj;
|
|
97
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
98
|
+
const key = keys[i];
|
|
99
|
+
if (current[key] === undefined || current[key] === null || typeof current[key] !== 'object') {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
current = current[key];
|
|
103
|
+
}
|
|
104
|
+
const lastKey = keys[keys.length - 1];
|
|
105
|
+
if (lastKey in current) {
|
|
106
|
+
delete current[lastKey];
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Coerce a string value to its appropriate type.
|
|
113
|
+
* - "true" / "false" -> boolean
|
|
114
|
+
* - Numeric strings -> number
|
|
115
|
+
* - Everything else -> string
|
|
116
|
+
*
|
|
117
|
+
* @param value - The string value to coerce
|
|
118
|
+
* @param forceString - If true, always return the value as a string
|
|
119
|
+
* @returns The coerced value
|
|
120
|
+
*/
|
|
121
|
+
export function coerceValue(value, forceString = false) {
|
|
122
|
+
if (forceString) {
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
// Boolean coercion
|
|
126
|
+
if (value === 'true') {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
if (value === 'false') {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
// Number coercion - must be a valid finite number
|
|
133
|
+
const num = Number(value);
|
|
134
|
+
if (!isNaN(num) && isFinite(num) && value.trim() !== '') {
|
|
135
|
+
return num;
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Format a value for YAML-like display.
|
|
141
|
+
*
|
|
142
|
+
* @param value - The value to format
|
|
143
|
+
* @param indent - Current indentation level
|
|
144
|
+
* @returns Formatted string
|
|
145
|
+
*/
|
|
146
|
+
export function formatValueYaml(value, indent = 0) {
|
|
147
|
+
const indentStr = ' '.repeat(indent);
|
|
148
|
+
if (value === null || value === undefined) {
|
|
149
|
+
return 'null';
|
|
150
|
+
}
|
|
151
|
+
if (typeof value === 'boolean' || typeof value === 'number') {
|
|
152
|
+
return String(value);
|
|
153
|
+
}
|
|
154
|
+
if (typeof value === 'string') {
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
if (value.length === 0) {
|
|
159
|
+
return '[]';
|
|
160
|
+
}
|
|
161
|
+
return value.map((item) => `${indentStr}- ${formatValueYaml(item, indent + 1)}`).join('\n');
|
|
162
|
+
}
|
|
163
|
+
if (typeof value === 'object') {
|
|
164
|
+
const entries = Object.entries(value);
|
|
165
|
+
if (entries.length === 0) {
|
|
166
|
+
return '{}';
|
|
167
|
+
}
|
|
168
|
+
return entries
|
|
169
|
+
.map(([key, val]) => {
|
|
170
|
+
const formattedVal = formatValueYaml(val, indent + 1);
|
|
171
|
+
if (typeof val === 'object' && val !== null && Object.keys(val).length > 0) {
|
|
172
|
+
return `${indentStr}${key}:\n${formattedVal}`;
|
|
173
|
+
}
|
|
174
|
+
return `${indentStr}${key}: ${formattedVal}`;
|
|
175
|
+
})
|
|
176
|
+
.join('\n');
|
|
177
|
+
}
|
|
178
|
+
return String(value);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Validate a configuration object against the schema.
|
|
182
|
+
*
|
|
183
|
+
* @param config - The configuration to validate
|
|
184
|
+
* @returns Validation result with success status and optional error message
|
|
185
|
+
*/
|
|
186
|
+
export function validateConfig(config) {
|
|
187
|
+
try {
|
|
188
|
+
GlobalConfigSchema.parse(config);
|
|
189
|
+
return { success: true };
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (error instanceof z.ZodError) {
|
|
193
|
+
const zodError = error;
|
|
194
|
+
const messages = zodError.issues.map((e) => `${e.path.join('.')}: ${e.message}`);
|
|
195
|
+
return { success: false, error: messages.join('; ') };
|
|
196
|
+
}
|
|
197
|
+
return { success: false, error: 'Unknown validation error' };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=config-schema.js.map
|
package/dist/core/config.js
CHANGED
|
@@ -4,24 +4,26 @@ export const OPENSPEC_MARKERS = {
|
|
|
4
4
|
end: '<!-- OPENSPEC:END -->'
|
|
5
5
|
};
|
|
6
6
|
export const AI_TOOLS = [
|
|
7
|
+
{ name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer' },
|
|
8
|
+
{ name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity' },
|
|
7
9
|
{ name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie' },
|
|
8
10
|
{ name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code' },
|
|
9
11
|
{ name: 'Cline', value: 'cline', available: true, successLabel: 'Cline' },
|
|
10
|
-
{ name: '
|
|
12
|
+
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex' },
|
|
11
13
|
{ name: 'CodeBuddy Code (CLI)', value: 'codebuddy', available: true, successLabel: 'CodeBuddy Code' },
|
|
12
14
|
{ name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict' },
|
|
13
15
|
{ name: 'Crush', value: 'crush', available: true, successLabel: 'Crush' },
|
|
14
16
|
{ name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor' },
|
|
15
17
|
{ name: 'Factory Droid', value: 'factory', available: true, successLabel: 'Factory Droid' },
|
|
16
18
|
{ name: 'Gemini CLI', value: 'gemini', available: true, successLabel: 'Gemini CLI' },
|
|
17
|
-
{ name: '
|
|
19
|
+
{ name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot' },
|
|
20
|
+
{ name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow' },
|
|
18
21
|
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code' },
|
|
22
|
+
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode' },
|
|
19
23
|
{ name: 'Qoder (CLI)', value: 'qoder', available: true, successLabel: 'Qoder' },
|
|
20
|
-
{ name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf' },
|
|
21
|
-
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex' },
|
|
22
|
-
{ name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot' },
|
|
23
|
-
{ name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer' },
|
|
24
24
|
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code' },
|
|
25
|
+
{ name: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode' },
|
|
26
|
+
{ name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf' },
|
|
25
27
|
{ name: 'AGENTS.md (works with Amp, VS Code, …)', value: 'agents', available: false, successLabel: 'your AGENTS.md-compatible assistant' }
|
|
26
28
|
];
|
|
27
29
|
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ToolConfigurator } from "./base.js";
|
|
2
|
+
export declare class IflowConfigurator implements ToolConfigurator {
|
|
3
|
+
name: string;
|
|
4
|
+
configFileName: string;
|
|
5
|
+
isAvailable: boolean;
|
|
6
|
+
configure(projectPath: string, openspecDir: string): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=iflow.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { FileSystemUtils } from "../../utils/file-system.js";
|
|
3
|
+
import { TemplateManager } from "../templates/index.js";
|
|
4
|
+
import { OPENSPEC_MARKERS } from "../config.js";
|
|
5
|
+
export class IflowConfigurator {
|
|
6
|
+
name = "iFlow";
|
|
7
|
+
configFileName = "IFLOW.md";
|
|
8
|
+
isAvailable = true;
|
|
9
|
+
async configure(projectPath, openspecDir) {
|
|
10
|
+
const filePath = path.join(projectPath, this.configFileName);
|
|
11
|
+
const content = TemplateManager.getClaudeTemplate();
|
|
12
|
+
await FileSystemUtils.updateFileWithMarkers(filePath, content, OPENSPEC_MARKERS.start, OPENSPEC_MARKERS.end);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=iflow.js.map
|
|
@@ -3,6 +3,7 @@ import { ClineConfigurator } from './cline.js';
|
|
|
3
3
|
import { CodeBuddyConfigurator } from './codebuddy.js';
|
|
4
4
|
import { CostrictConfigurator } from './costrict.js';
|
|
5
5
|
import { QoderConfigurator } from './qoder.js';
|
|
6
|
+
import { IflowConfigurator } from './iflow.js';
|
|
6
7
|
import { AgentsStandardConfigurator } from './agents.js';
|
|
7
8
|
import { QwenConfigurator } from './qwen.js';
|
|
8
9
|
export class ToolRegistry {
|
|
@@ -13,6 +14,7 @@ export class ToolRegistry {
|
|
|
13
14
|
const codeBuddyConfigurator = new CodeBuddyConfigurator();
|
|
14
15
|
const costrictConfigurator = new CostrictConfigurator();
|
|
15
16
|
const qoderConfigurator = new QoderConfigurator();
|
|
17
|
+
const iflowConfigurator = new IflowConfigurator();
|
|
16
18
|
const agentsConfigurator = new AgentsStandardConfigurator();
|
|
17
19
|
const qwenConfigurator = new QwenConfigurator();
|
|
18
20
|
// Register with the ID that matches the checkbox value
|
|
@@ -21,6 +23,7 @@ export class ToolRegistry {
|
|
|
21
23
|
this.tools.set('codebuddy', codeBuddyConfigurator);
|
|
22
24
|
this.tools.set('costrict', costrictConfigurator);
|
|
23
25
|
this.tools.set('qoder', qoderConfigurator);
|
|
26
|
+
this.tools.set('iflow', iflowConfigurator);
|
|
24
27
|
this.tools.set('agents', agentsConfigurator);
|
|
25
28
|
this.tools.set('qwen', qwenConfigurator);
|
|
26
29
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SlashCommandConfigurator } from './base.js';
|
|
2
|
+
import { SlashCommandId } from '../../templates/index.js';
|
|
3
|
+
export declare class AntigravitySlashCommandConfigurator extends SlashCommandConfigurator {
|
|
4
|
+
readonly toolId = "antigravity";
|
|
5
|
+
readonly isAvailable = true;
|
|
6
|
+
protected getRelativePath(id: SlashCommandId): string;
|
|
7
|
+
protected getFrontmatter(id: SlashCommandId): string | undefined;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=antigravity.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SlashCommandConfigurator } from './base.js';
|
|
2
|
+
const FILE_PATHS = {
|
|
3
|
+
proposal: '.agent/workflows/openspec-proposal.md',
|
|
4
|
+
apply: '.agent/workflows/openspec-apply.md',
|
|
5
|
+
archive: '.agent/workflows/openspec-archive.md'
|
|
6
|
+
};
|
|
7
|
+
const DESCRIPTIONS = {
|
|
8
|
+
proposal: 'Scaffold a new OpenSpec change and validate strictly.',
|
|
9
|
+
apply: 'Implement an approved OpenSpec change and keep tasks in sync.',
|
|
10
|
+
archive: 'Archive a deployed OpenSpec change and update specs.'
|
|
11
|
+
};
|
|
12
|
+
export class AntigravitySlashCommandConfigurator extends SlashCommandConfigurator {
|
|
13
|
+
toolId = 'antigravity';
|
|
14
|
+
isAvailable = true;
|
|
15
|
+
getRelativePath(id) {
|
|
16
|
+
return FILE_PATHS[id];
|
|
17
|
+
}
|
|
18
|
+
getFrontmatter(id) {
|
|
19
|
+
const description = DESCRIPTIONS[id];
|
|
20
|
+
return `---\ndescription: ${description}\n---`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=antigravity.js.map
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TomlSlashCommandConfigurator } from './toml-base.js';
|
|
2
2
|
import { SlashCommandId } from '../../templates/index.js';
|
|
3
|
-
export declare class GeminiSlashCommandConfigurator extends
|
|
3
|
+
export declare class GeminiSlashCommandConfigurator extends TomlSlashCommandConfigurator {
|
|
4
4
|
readonly toolId = "gemini";
|
|
5
5
|
readonly isAvailable = true;
|
|
6
6
|
protected getRelativePath(id: SlashCommandId): string;
|
|
7
|
-
protected
|
|
8
|
-
generateAll(projectPath: string, _openspecDir: string): Promise<string[]>;
|
|
9
|
-
private generateTOML;
|
|
10
|
-
protected updateBody(filePath: string, body: string): Promise<void>;
|
|
7
|
+
protected getDescription(id: SlashCommandId): string;
|
|
11
8
|
}
|
|
12
9
|
//# sourceMappingURL=gemini.d.ts.map
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SlashCommandConfigurator } from './base.js';
|
|
3
|
-
import { OPENSPEC_MARKERS } from '../../config.js';
|
|
1
|
+
import { TomlSlashCommandConfigurator } from './toml-base.js';
|
|
4
2
|
const FILE_PATHS = {
|
|
5
3
|
proposal: '.gemini/commands/openspec/proposal.toml',
|
|
6
4
|
apply: '.gemini/commands/openspec/apply.toml',
|
|
@@ -11,58 +9,14 @@ const DESCRIPTIONS = {
|
|
|
11
9
|
apply: 'Implement an approved OpenSpec change and keep tasks in sync.',
|
|
12
10
|
archive: 'Archive a deployed OpenSpec change and update specs.'
|
|
13
11
|
};
|
|
14
|
-
export class GeminiSlashCommandConfigurator extends
|
|
12
|
+
export class GeminiSlashCommandConfigurator extends TomlSlashCommandConfigurator {
|
|
15
13
|
toolId = 'gemini';
|
|
16
14
|
isAvailable = true;
|
|
17
15
|
getRelativePath(id) {
|
|
18
16
|
return FILE_PATHS[id];
|
|
19
17
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|
|
24
|
-
// Override to generate TOML format with markers inside the prompt field
|
|
25
|
-
async generateAll(projectPath, _openspecDir) {
|
|
26
|
-
const createdOrUpdated = [];
|
|
27
|
-
for (const target of this.getTargets()) {
|
|
28
|
-
const body = this.getBody(target.id);
|
|
29
|
-
const filePath = FileSystemUtils.joinPath(projectPath, target.path);
|
|
30
|
-
if (await FileSystemUtils.fileExists(filePath)) {
|
|
31
|
-
await this.updateBody(filePath, body);
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
const tomlContent = this.generateTOML(target.id, body);
|
|
35
|
-
await FileSystemUtils.writeFile(filePath, tomlContent);
|
|
36
|
-
}
|
|
37
|
-
createdOrUpdated.push(target.path);
|
|
38
|
-
}
|
|
39
|
-
return createdOrUpdated;
|
|
40
|
-
}
|
|
41
|
-
generateTOML(id, body) {
|
|
42
|
-
const description = DESCRIPTIONS[id];
|
|
43
|
-
// TOML format with triple-quoted string for multi-line prompt
|
|
44
|
-
// Markers are inside the prompt value
|
|
45
|
-
return `description = "${description}"
|
|
46
|
-
|
|
47
|
-
prompt = """
|
|
48
|
-
${OPENSPEC_MARKERS.start}
|
|
49
|
-
${body}
|
|
50
|
-
${OPENSPEC_MARKERS.end}
|
|
51
|
-
"""
|
|
52
|
-
`;
|
|
53
|
-
}
|
|
54
|
-
// Override updateBody to handle TOML format
|
|
55
|
-
async updateBody(filePath, body) {
|
|
56
|
-
const content = await FileSystemUtils.readFile(filePath);
|
|
57
|
-
const startIndex = content.indexOf(OPENSPEC_MARKERS.start);
|
|
58
|
-
const endIndex = content.indexOf(OPENSPEC_MARKERS.end);
|
|
59
|
-
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
60
|
-
throw new Error(`Missing OpenSpec markers in ${filePath}`);
|
|
61
|
-
}
|
|
62
|
-
const before = content.slice(0, startIndex + OPENSPEC_MARKERS.start.length);
|
|
63
|
-
const after = content.slice(endIndex);
|
|
64
|
-
const updatedContent = `${before}\n${body}\n${after}`;
|
|
65
|
-
await FileSystemUtils.writeFile(filePath, updatedContent);
|
|
18
|
+
getDescription(id) {
|
|
19
|
+
return DESCRIPTIONS[id];
|
|
66
20
|
}
|
|
67
21
|
}
|
|
68
22
|
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SlashCommandConfigurator } from './base.js';
|
|
2
|
+
import { SlashCommandId } from '../../templates/index.js';
|
|
3
|
+
export declare class IflowSlashCommandConfigurator extends SlashCommandConfigurator {
|
|
4
|
+
readonly toolId = "iflow";
|
|
5
|
+
readonly isAvailable = true;
|
|
6
|
+
protected getRelativePath(id: SlashCommandId): string;
|
|
7
|
+
protected getFrontmatter(id: SlashCommandId): string;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=iflow.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SlashCommandConfigurator } from './base.js';
|
|
2
|
+
const FILE_PATHS = {
|
|
3
|
+
proposal: '.iflow/commands/openspec-proposal.md',
|
|
4
|
+
apply: '.iflow/commands/openspec-apply.md',
|
|
5
|
+
archive: '.iflow/commands/openspec-archive.md'
|
|
6
|
+
};
|
|
7
|
+
const FRONTMATTER = {
|
|
8
|
+
proposal: `---
|
|
9
|
+
name: /openspec-proposal
|
|
10
|
+
id: openspec-proposal
|
|
11
|
+
category: OpenSpec
|
|
12
|
+
description: Scaffold a new OpenSpec change and validate strictly.
|
|
13
|
+
---`,
|
|
14
|
+
apply: `---
|
|
15
|
+
name: /openspec-apply
|
|
16
|
+
id: openspec-apply
|
|
17
|
+
category: OpenSpec
|
|
18
|
+
description: Implement an approved OpenSpec change and keep tasks in sync.
|
|
19
|
+
---`,
|
|
20
|
+
archive: `---
|
|
21
|
+
name: /openspec-archive
|
|
22
|
+
id: openspec-archive
|
|
23
|
+
category: OpenSpec
|
|
24
|
+
description: Archive a deployed OpenSpec change and update specs.
|
|
25
|
+
---`
|
|
26
|
+
};
|
|
27
|
+
export class IflowSlashCommandConfigurator extends SlashCommandConfigurator {
|
|
28
|
+
toolId = 'iflow';
|
|
29
|
+
isAvailable = true;
|
|
30
|
+
getRelativePath(id) {
|
|
31
|
+
return FILE_PATHS[id];
|
|
32
|
+
}
|
|
33
|
+
getFrontmatter(id) {
|
|
34
|
+
return FRONTMATTER[id];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=iflow.js.map
|
|
@@ -8,7 +8,6 @@ const FILE_PATHS = {
|
|
|
8
8
|
};
|
|
9
9
|
const FRONTMATTER = {
|
|
10
10
|
proposal: `---
|
|
11
|
-
agent: build
|
|
12
11
|
description: Scaffold a new OpenSpec change and validate strictly.
|
|
13
12
|
---
|
|
14
13
|
The user has requested the following change proposal. Use the openspec instructions to create their change proposal.
|
|
@@ -17,7 +16,6 @@ The user has requested the following change proposal. Use the openspec instructi
|
|
|
17
16
|
</UserRequest>
|
|
18
17
|
`,
|
|
19
18
|
apply: `---
|
|
20
|
-
agent: build
|
|
21
19
|
description: Implement an approved OpenSpec change and keep tasks in sync.
|
|
22
20
|
---
|
|
23
21
|
The user has requested to implement the following change proposal. Find the change proposal and follow the instructions below. If you're not sure or if ambiguous, ask for clarification from the user.
|
|
@@ -26,7 +24,6 @@ The user has requested to implement the following change proposal. Find the chan
|
|
|
26
24
|
</UserRequest>
|
|
27
25
|
`,
|
|
28
26
|
archive: `---
|
|
29
|
-
agent: build
|
|
30
27
|
description: Archive a deployed OpenSpec change and update specs.
|
|
31
28
|
---
|
|
32
29
|
<ChangeId>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @implements {SlashCommandConfigurator}
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { TomlSlashCommandConfigurator } from './toml-base.js';
|
|
9
9
|
import { SlashCommandId } from '../../templates/index.js';
|
|
10
10
|
/**
|
|
11
11
|
* QwenSlashCommandConfigurator class provides integration with Qwen Code
|
|
@@ -16,7 +16,7 @@ import { SlashCommandId } from '../../templates/index.js';
|
|
|
16
16
|
* - /openspec-apply: Apply an approved OpenSpec change
|
|
17
17
|
* - /openspec-archive: Archive a deployed OpenSpec change
|
|
18
18
|
*/
|
|
19
|
-
export declare class QwenSlashCommandConfigurator extends
|
|
19
|
+
export declare class QwenSlashCommandConfigurator extends TomlSlashCommandConfigurator {
|
|
20
20
|
/** Unique identifier for the Qwen tool */
|
|
21
21
|
readonly toolId = "qwen";
|
|
22
22
|
/** Availability status for the Qwen tool */
|
|
@@ -27,11 +27,6 @@ export declare class QwenSlashCommandConfigurator extends SlashCommandConfigurat
|
|
|
27
27
|
* @returns {string} The relative path to the command file
|
|
28
28
|
*/
|
|
29
29
|
protected getRelativePath(id: SlashCommandId): string;
|
|
30
|
-
|
|
31
|
-
* Returns the YAML frontmatter for a given slash command ID.
|
|
32
|
-
* @param {SlashCommandId} id - The slash command identifier
|
|
33
|
-
* @returns {string} The YAML frontmatter string
|
|
34
|
-
*/
|
|
35
|
-
protected getFrontmatter(id: SlashCommandId): string;
|
|
30
|
+
protected getDescription(id: SlashCommandId): string;
|
|
36
31
|
}
|
|
37
32
|
//# sourceMappingURL=qwen.d.ts.map
|
|
@@ -5,40 +5,20 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @implements {SlashCommandConfigurator}
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { TomlSlashCommandConfigurator } from './toml-base.js';
|
|
9
9
|
/**
|
|
10
10
|
* Mapping of slash command IDs to their corresponding file paths in .qwen/commands directory.
|
|
11
11
|
* @type {Record<SlashCommandId, string>}
|
|
12
12
|
*/
|
|
13
13
|
const FILE_PATHS = {
|
|
14
|
-
proposal: '.qwen/commands/openspec-proposal.
|
|
15
|
-
apply: '.qwen/commands/openspec-apply.
|
|
16
|
-
archive: '.qwen/commands/openspec-archive.
|
|
14
|
+
proposal: '.qwen/commands/openspec-proposal.toml',
|
|
15
|
+
apply: '.qwen/commands/openspec-apply.toml',
|
|
16
|
+
archive: '.qwen/commands/openspec-archive.toml'
|
|
17
17
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*/
|
|
23
|
-
const FRONTMATTER = {
|
|
24
|
-
proposal: `---
|
|
25
|
-
name: /openspec-proposal
|
|
26
|
-
id: openspec-proposal
|
|
27
|
-
category: OpenSpec
|
|
28
|
-
description: Scaffold a new OpenSpec change and validate strictly.
|
|
29
|
-
---`,
|
|
30
|
-
apply: `---
|
|
31
|
-
name: /openspec-apply
|
|
32
|
-
id: openspec-apply
|
|
33
|
-
category: OpenSpec
|
|
34
|
-
description: Implement an approved OpenSpec change and keep tasks in sync.
|
|
35
|
-
---`,
|
|
36
|
-
archive: `---
|
|
37
|
-
name: /openspec-archive
|
|
38
|
-
id: openspec-archive
|
|
39
|
-
category: OpenSpec
|
|
40
|
-
description: Archive a deployed OpenSpec change and update specs.
|
|
41
|
-
---`
|
|
18
|
+
const DESCRIPTIONS = {
|
|
19
|
+
proposal: 'Scaffold a new OpenSpec change and validate strictly.',
|
|
20
|
+
apply: 'Implement an approved OpenSpec change and keep tasks in sync.',
|
|
21
|
+
archive: 'Archive a deployed OpenSpec change and update specs.'
|
|
42
22
|
};
|
|
43
23
|
/**
|
|
44
24
|
* QwenSlashCommandConfigurator class provides integration with Qwen Code
|
|
@@ -49,7 +29,7 @@ description: Archive a deployed OpenSpec change and update specs.
|
|
|
49
29
|
* - /openspec-apply: Apply an approved OpenSpec change
|
|
50
30
|
* - /openspec-archive: Archive a deployed OpenSpec change
|
|
51
31
|
*/
|
|
52
|
-
export class QwenSlashCommandConfigurator extends
|
|
32
|
+
export class QwenSlashCommandConfigurator extends TomlSlashCommandConfigurator {
|
|
53
33
|
/** Unique identifier for the Qwen tool */
|
|
54
34
|
toolId = 'qwen';
|
|
55
35
|
/** Availability status for the Qwen tool */
|
|
@@ -62,13 +42,8 @@ export class QwenSlashCommandConfigurator extends SlashCommandConfigurator {
|
|
|
62
42
|
getRelativePath(id) {
|
|
63
43
|
return FILE_PATHS[id];
|
|
64
44
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
* @param {SlashCommandId} id - The slash command identifier
|
|
68
|
-
* @returns {string} The YAML frontmatter string
|
|
69
|
-
*/
|
|
70
|
-
getFrontmatter(id) {
|
|
71
|
-
return FRONTMATTER[id];
|
|
45
|
+
getDescription(id) {
|
|
46
|
+
return DESCRIPTIONS[id];
|
|
72
47
|
}
|
|
73
48
|
}
|
|
74
49
|
//# sourceMappingURL=qwen.js.map
|
|
@@ -16,6 +16,8 @@ import { CrushSlashCommandConfigurator } from './crush.js';
|
|
|
16
16
|
import { CostrictSlashCommandConfigurator } from './costrict.js';
|
|
17
17
|
import { QwenSlashCommandConfigurator } from './qwen.js';
|
|
18
18
|
import { RooCodeSlashCommandConfigurator } from './roocode.js';
|
|
19
|
+
import { AntigravitySlashCommandConfigurator } from './antigravity.js';
|
|
20
|
+
import { IflowSlashCommandConfigurator } from './iflow.js';
|
|
19
21
|
export class SlashCommandRegistry {
|
|
20
22
|
static configurators = new Map();
|
|
21
23
|
static {
|
|
@@ -37,6 +39,8 @@ export class SlashCommandRegistry {
|
|
|
37
39
|
const costrict = new CostrictSlashCommandConfigurator();
|
|
38
40
|
const qwen = new QwenSlashCommandConfigurator();
|
|
39
41
|
const roocode = new RooCodeSlashCommandConfigurator();
|
|
42
|
+
const antigravity = new AntigravitySlashCommandConfigurator();
|
|
43
|
+
const iflow = new IflowSlashCommandConfigurator();
|
|
40
44
|
this.configurators.set(claude.toolId, claude);
|
|
41
45
|
this.configurators.set(codeBuddy.toolId, codeBuddy);
|
|
42
46
|
this.configurators.set(qoder.toolId, qoder);
|
|
@@ -55,6 +59,8 @@ export class SlashCommandRegistry {
|
|
|
55
59
|
this.configurators.set(costrict.toolId, costrict);
|
|
56
60
|
this.configurators.set(qwen.toolId, qwen);
|
|
57
61
|
this.configurators.set(roocode.toolId, roocode);
|
|
62
|
+
this.configurators.set(antigravity.toolId, antigravity);
|
|
63
|
+
this.configurators.set(iflow.toolId, iflow);
|
|
58
64
|
}
|
|
59
65
|
static register(configurator) {
|
|
60
66
|
this.configurators.set(configurator.toolId, configurator);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SlashCommandConfigurator } from './base.js';
|
|
2
|
+
import { SlashCommandId } from '../../templates/index.js';
|
|
3
|
+
export declare abstract class TomlSlashCommandConfigurator extends SlashCommandConfigurator {
|
|
4
|
+
protected getFrontmatter(_id: SlashCommandId): string | undefined;
|
|
5
|
+
protected abstract getDescription(id: SlashCommandId): string;
|
|
6
|
+
generateAll(projectPath: string, _openspecDir: string): Promise<string[]>;
|
|
7
|
+
private generateTOML;
|
|
8
|
+
protected updateBody(filePath: string, body: string): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=toml-base.d.ts.map
|