@fission-ai/openspec 1.10.0 → 1.12.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 +1 -0
- package/dist/cli/index.js +12 -2
- package/dist/commands/change.d.ts +24 -0
- package/dist/commands/change.js +229 -5
- package/dist/commands/schema.d.ts +5 -0
- package/dist/commands/schema.js +201 -35
- package/dist/commands/show.js +1 -1
- package/dist/commands/validate.d.ts +57 -0
- package/dist/commands/validate.js +99 -13
- package/dist/commands/workflow/index.d.ts +1 -1
- package/dist/commands/workflow/index.js +1 -1
- package/dist/commands/workflow/status.d.ts +2 -0
- package/dist/commands/workflow/status.js +76 -12
- package/dist/core/artifact-graph/resolver.js +15 -9
- package/dist/core/available-tools.js +16 -1
- package/dist/core/command-generation/adapters/antigravity.d.ts +1 -1
- package/dist/core/command-generation/adapters/antigravity.js +2 -2
- package/dist/core/command-generation/adapters/codeassistant.d.ts +15 -0
- package/dist/core/command-generation/adapters/codeassistant.js +29 -0
- package/dist/core/command-generation/adapters/index.d.ts +1 -0
- package/dist/core/command-generation/adapters/index.js +1 -0
- package/dist/core/command-generation/registry.js +2 -0
- package/dist/core/completions/command-registry.js +21 -0
- package/dist/core/completions/generators/fish-generator.d.ts +12 -0
- package/dist/core/completions/generators/fish-generator.js +79 -47
- package/dist/core/completions/templates/fish-templates.d.ts +1 -1
- package/dist/core/completions/templates/fish-templates.js +107 -10
- package/dist/core/completions/types.d.ts +4 -0
- package/dist/core/config.js +7 -1
- package/dist/core/init.d.ts +1 -0
- package/dist/core/init.js +71 -65
- package/dist/core/legacy-cleanup.js +6 -0
- package/dist/core/migration.js +42 -12
- package/dist/core/openspec-root.d.ts +1 -0
- package/dist/core/openspec-root.js +10 -2
- package/dist/core/parsers/requirement-blocks.d.ts +1 -0
- package/dist/core/parsers/requirement-blocks.js +2 -0
- package/dist/core/shared/ide-restart.d.ts +26 -0
- package/dist/core/shared/ide-restart.js +42 -0
- package/dist/core/shared/index.d.ts +1 -0
- package/dist/core/shared/index.js +1 -0
- package/dist/core/shared/tool-detection.js +9 -3
- package/dist/core/shared-skill-target.d.ts +13 -0
- package/dist/core/shared-skill-target.js +43 -1
- package/dist/core/specs-apply.js +22 -9
- package/dist/core/templates/workflows/explore.js +108 -73
- package/dist/core/templates/workflows/ff-change.js +8 -0
- package/dist/core/templates/workflows/propose.js +8 -0
- package/dist/core/update.js +28 -31
- package/dist/core/validation/constants.d.ts +3 -0
- package/dist/core/validation/constants.js +12 -0
- package/dist/core/validation/purpose-placeholder.d.ts +45 -0
- package/dist/core/validation/purpose-placeholder.js +117 -0
- package/dist/core/validation/validator.d.ts +12 -3
- package/dist/core/validation/validator.js +90 -4
- package/dist/utils/command-references.js +7 -8
- package/dist/utils/requirement-diff.d.ts +42 -0
- package/dist/utils/requirement-diff.js +61 -0
- package/package.json +4 -3
|
@@ -24,6 +24,18 @@ export declare class FishGenerator implements CompletionGenerator {
|
|
|
24
24
|
* Generate positional argument completion
|
|
25
25
|
*/
|
|
26
26
|
private generatePositionalCompletion;
|
|
27
|
+
/**
|
|
28
|
+
* Generate indexed positional completions.
|
|
29
|
+
*/
|
|
30
|
+
private generateIndexedPositionalCompletions;
|
|
31
|
+
/**
|
|
32
|
+
* Collect the long and short names for flags that consume the next token.
|
|
33
|
+
*/
|
|
34
|
+
private collectValueFlags;
|
|
35
|
+
/**
|
|
36
|
+
* Build a Fish condition that matches command words in their actual slots.
|
|
37
|
+
*/
|
|
38
|
+
private commandPathCondition;
|
|
27
39
|
/**
|
|
28
40
|
* Escape description text for Fish
|
|
29
41
|
*/
|
|
@@ -12,30 +12,26 @@ export class FishGenerator {
|
|
|
12
12
|
* @returns Fish completion script as a string
|
|
13
13
|
*/
|
|
14
14
|
generate(commands) {
|
|
15
|
-
// Build top-level commands using push() for loop clarity
|
|
16
15
|
const topLevelLines = [];
|
|
17
16
|
for (const cmd of commands) {
|
|
18
17
|
topLevelLines.push(`# ${cmd.name} command`);
|
|
19
|
-
topLevelLines.push(`complete -c openspec -n '__fish_openspec_no_subcommand' -a '${cmd.name}' -d '${this.escapeDescription(cmd.description)}'`);
|
|
18
|
+
topLevelLines.push(`complete -c openspec -n '__fish_openspec_no_subcommand' -f -a '${cmd.name}' -d '${this.escapeDescription(cmd.description)}'`);
|
|
20
19
|
}
|
|
21
20
|
const topLevelCommands = topLevelLines.join('\n');
|
|
22
|
-
// Build command-specific completions using push() for loop clarity
|
|
23
21
|
const commandCompletionLines = [];
|
|
24
22
|
for (const cmd of commands) {
|
|
25
23
|
commandCompletionLines.push(...this.generateCommandCompletions(cmd));
|
|
26
24
|
commandCompletionLines.push('');
|
|
27
25
|
}
|
|
28
26
|
const commandCompletions = commandCompletionLines.join('\n');
|
|
29
|
-
// Static helper functions from template
|
|
30
27
|
const helperFunctions = FISH_STATIC_HELPERS;
|
|
31
|
-
// Dynamic completion helpers from template
|
|
32
28
|
const dynamicHelpers = FISH_DYNAMIC_HELPERS;
|
|
33
|
-
// Assemble final script with template literal
|
|
34
29
|
return `# Fish completion script for OpenSpec CLI
|
|
35
30
|
# Auto-generated - do not edit manually
|
|
36
31
|
|
|
37
32
|
${helperFunctions}
|
|
38
33
|
${dynamicHelpers}
|
|
34
|
+
complete -c openspec -l no-color -f -d 'Disable color output'
|
|
39
35
|
${topLevelCommands}
|
|
40
36
|
|
|
41
37
|
${commandCompletions}`;
|
|
@@ -45,38 +41,45 @@ ${commandCompletions}`;
|
|
|
45
41
|
*/
|
|
46
42
|
generateCommandCompletions(cmd) {
|
|
47
43
|
const lines = [];
|
|
48
|
-
// If command has subcommands
|
|
49
44
|
if (cmd.subcommands && cmd.subcommands.length > 0) {
|
|
50
|
-
|
|
45
|
+
const commandCondition = this.commandPathCondition([cmd.name]);
|
|
46
|
+
const parentValueFlags = this.collectValueFlags(cmd.flags);
|
|
47
|
+
const noSubcommandCondition = cmd.subcommands
|
|
48
|
+
.map((subcmd) => `not ${this.commandPathCondition([cmd.name, subcmd.name], parentValueFlags)}`)
|
|
49
|
+
.join('; and ');
|
|
51
50
|
for (const subcmd of cmd.subcommands) {
|
|
52
|
-
lines.push(`complete -c openspec -n '
|
|
51
|
+
lines.push(`complete -c openspec -n '${commandCondition}; and ${noSubcommandCondition}' -f -a '${subcmd.name}' -d '${this.escapeDescription(subcmd.description)}'`);
|
|
53
52
|
}
|
|
54
53
|
lines.push('');
|
|
55
|
-
// Add flags for parent command
|
|
56
54
|
for (const flag of cmd.flags) {
|
|
57
|
-
lines.push(...this.generateFlagCompletion(flag,
|
|
55
|
+
lines.push(...this.generateFlagCompletion(flag, commandCondition));
|
|
58
56
|
}
|
|
59
|
-
// Add completions for each subcommand
|
|
60
57
|
for (const subcmd of cmd.subcommands) {
|
|
58
|
+
const subcommandCondition = this.commandPathCondition([cmd.name, subcmd.name], parentValueFlags);
|
|
61
59
|
lines.push(`# ${cmd.name} ${subcmd.name} flags`);
|
|
60
|
+
lines.push(`complete -c openspec -n '${subcommandCondition}' -f`);
|
|
62
61
|
for (const flag of subcmd.flags) {
|
|
63
|
-
lines.push(...this.generateFlagCompletion(flag,
|
|
62
|
+
lines.push(...this.generateFlagCompletion(flag, subcommandCondition));
|
|
64
63
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
if (subcmd.positionals?.length) {
|
|
65
|
+
lines.push(...this.generateIndexedPositionalCompletions(subcmd.positionals, subcommandCondition, this.collectValueFlags(cmd.flags, subcmd.flags), 2));
|
|
66
|
+
}
|
|
67
|
+
else if (subcmd.acceptsPositional) {
|
|
68
|
+
lines.push(...this.generatePositionalCompletion(subcmd.positionalType, subcommandCondition));
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
else {
|
|
72
|
-
// Command without subcommands
|
|
73
73
|
lines.push(`# ${cmd.name} flags`);
|
|
74
|
+
lines.push(`complete -c openspec -n '__fish_openspec_using_command_path ${cmd.name}' -f`);
|
|
74
75
|
for (const flag of cmd.flags) {
|
|
75
|
-
lines.push(...this.generateFlagCompletion(flag, `
|
|
76
|
+
lines.push(...this.generateFlagCompletion(flag, `__fish_openspec_using_command_path ${cmd.name}`));
|
|
77
|
+
}
|
|
78
|
+
if (cmd.positionals?.length) {
|
|
79
|
+
lines.push(...this.generateIndexedPositionalCompletions(cmd.positionals, `__fish_openspec_using_command_path ${cmd.name}`, this.collectValueFlags(cmd.flags), 1));
|
|
76
80
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
lines.push(...this.generatePositionalCompletion(cmd.positionalType, `__fish_openspec_using_subcommand ${cmd.name}`));
|
|
81
|
+
else if (cmd.acceptsPositional) {
|
|
82
|
+
lines.push(...this.generatePositionalCompletion(cmd.positionalType, `__fish_openspec_using_command_path ${cmd.name}`));
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
return lines;
|
|
@@ -86,36 +89,26 @@ ${commandCompletions}`;
|
|
|
86
89
|
*/
|
|
87
90
|
generateFlagCompletion(flag, condition) {
|
|
88
91
|
const lines = [];
|
|
89
|
-
const
|
|
90
|
-
const shortFlag = flag.short ?
|
|
92
|
+
const description = this.escapeDescription(flag.description);
|
|
93
|
+
const shortFlag = flag.short ? `-s ${flag.short} ` : '';
|
|
94
|
+
const flagOptions = `${shortFlag}-l ${flag.name}`;
|
|
91
95
|
if (flag.takesValue && flag.values) {
|
|
92
|
-
// Flag with enum values
|
|
93
96
|
for (const value of flag.values) {
|
|
94
|
-
|
|
95
|
-
lines.push(`complete -c openspec -n '${condition}' -s ${flag.short} -l ${flag.name} -a '${value}' -d '${this.escapeDescription(flag.description)}'`);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
lines.push(`complete -c openspec -n '${condition}' -l ${flag.name} -a '${value}' -d '${this.escapeDescription(flag.description)}'`);
|
|
99
|
-
}
|
|
97
|
+
lines.push(`complete -c openspec -n '${condition}' ${flagOptions} -r -f -a '${value}' -d '${description}'`);
|
|
100
98
|
}
|
|
101
99
|
}
|
|
102
100
|
else if (flag.takesValue) {
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
lines.push(`complete -c openspec -n '${condition}' ${flagOptions} -r -f -d '${description}'`);
|
|
102
|
+
if (flag.completionType === 'path') {
|
|
103
|
+
const optionNames = [`--${flag.name}`, ...(flag.short ? [`-${flag.short}`] : [])];
|
|
104
|
+
lines.push(`complete -c openspec -n '${condition}; and __fish_openspec_completing_option_value ${optionNames.join(' ')}' ${flagOptions} -r -F -d '${description}'`);
|
|
105
|
+
if (flag.short) {
|
|
106
|
+
lines.push(`complete -c openspec -n '${condition}' ${flagOptions} -r -f -a '(__fish_openspec_complete_attached_short_path -${flag.short})' -d '${description}'`);
|
|
107
|
+
}
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
110
|
else {
|
|
112
|
-
|
|
113
|
-
if (shortFlag) {
|
|
114
|
-
lines.push(`complete -c openspec -n '${condition}' -s ${flag.short} -l ${flag.name} -d '${this.escapeDescription(flag.description)}'`);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
lines.push(`complete -c openspec -n '${condition}' -l ${flag.name} -d '${this.escapeDescription(flag.description)}'`);
|
|
118
|
-
}
|
|
111
|
+
lines.push(`complete -c openspec -n '${condition}' ${flagOptions} -f -d '${description}'`);
|
|
119
112
|
}
|
|
120
113
|
return lines;
|
|
121
114
|
}
|
|
@@ -141,20 +134,59 @@ ${commandCompletions}`;
|
|
|
141
134
|
lines.push(`complete -c openspec -n '${condition}' -a 'zsh bash fish powershell' -f`);
|
|
142
135
|
break;
|
|
143
136
|
case 'path':
|
|
144
|
-
//
|
|
137
|
+
// -F re-enables filesystem completion: sibling rules in the same
|
|
138
|
+
// context carry -f, and Fish never restores files without --force-files.
|
|
139
|
+
lines.push(`complete -c openspec -n '${condition}' -F`);
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
lines.push(`complete -c openspec -n '${condition}' -f`);
|
|
145
143
|
break;
|
|
146
144
|
}
|
|
147
145
|
return lines;
|
|
148
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Generate indexed positional completions.
|
|
149
|
+
*/
|
|
150
|
+
generateIndexedPositionalCompletions(positionals, condition, valueFlags, depth) {
|
|
151
|
+
const lines = [];
|
|
152
|
+
for (const [index, positional] of positionals.entries()) {
|
|
153
|
+
const indexCondition = `${condition}; and __fish_openspec_positional_index ${index} ${depth}${valueFlags.length ? ` ${valueFlags.join(' ')}` : ''}`;
|
|
154
|
+
lines.push(...this.generatePositionalCompletion(positional.type, indexCondition));
|
|
155
|
+
}
|
|
156
|
+
return lines;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Collect the long and short names for flags that consume the next token.
|
|
160
|
+
*/
|
|
161
|
+
collectValueFlags(...flagGroups) {
|
|
162
|
+
const flags = new Set();
|
|
163
|
+
for (const group of flagGroups) {
|
|
164
|
+
for (const flag of group) {
|
|
165
|
+
if (!flag.takesValue) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
flags.add(`--${flag.name}`);
|
|
169
|
+
if (flag.short) {
|
|
170
|
+
flags.add(`-${flag.short}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return [...flags];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Build a Fish condition that matches command words in their actual slots.
|
|
178
|
+
*/
|
|
179
|
+
commandPathCondition(path, valueFlags = []) {
|
|
180
|
+
const valueFlagArguments = valueFlags.length ? ` -- ${valueFlags.join(' ')}` : '';
|
|
181
|
+
return `__fish_openspec_using_command_path ${path.join(' ')}${valueFlagArguments}`;
|
|
182
|
+
}
|
|
149
183
|
/**
|
|
150
184
|
* Escape description text for Fish
|
|
151
185
|
*/
|
|
152
186
|
escapeDescription(description) {
|
|
153
187
|
return description
|
|
154
188
|
.replace(/\\/g, '\\\\') // Backslashes first
|
|
155
|
-
.replace(/'/g, "\\'") // Single quotes
|
|
156
|
-
.replace(/\$/g, '\\$') // Dollar signs (prevents $())
|
|
157
|
-
.replace(/`/g, '\\`'); // Backticks
|
|
189
|
+
.replace(/'/g, "\\'"); // Single quotes
|
|
158
190
|
}
|
|
159
191
|
}
|
|
160
192
|
//# sourceMappingURL=fish-generator.js.map
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Static template strings for Fish completion scripts.
|
|
3
3
|
* These are Fish-specific helper functions that never change.
|
|
4
4
|
*/
|
|
5
|
-
export declare const FISH_STATIC_HELPERS = "# Helper function to
|
|
5
|
+
export declare const FISH_STATIC_HELPERS = "# Helper function to match the command path at the start of the invocation\nfunction __fish_openspec_using_command_path\n set -l expected\n set -l value_flags\n set -l reading_value_flags 0\n for argument in $argv\n if test \"$argument\" = --\n set reading_value_flags 1\n continue\n end\n if test $reading_value_flags -eq 1\n set -a value_flags $argument\n else\n set -a expected $argument\n end\n end\n\n set -l tokens (commandline -opc)\n set -e tokens[1]\n set -l path\n set -l skip 0\n for token in $tokens\n if test $skip -eq 1\n set skip 0\n continue\n end\n if test \"$token\" = --no-color\n continue\n end\n if contains -- $token $value_flags\n set skip 1\n continue\n end\n if string match -q -- '-*' $token\n continue\n end\n set -a path $token\n if test (count $path) -eq (count $expected)\n break\n end\n end\n test (count $path) -eq (count $expected); or return 1\n for index in (seq (count $expected))\n test \"$path[$index]\" = \"$expected[$index]\"; or return 1\n end\n return 0\nend\n\nfunction __fish_openspec_no_subcommand\n set -l tokens (commandline -opc)\n set -e tokens[1]\n for token in $tokens\n if test \"$token\" != --no-color\n return 1\n end\n end\n return 0\nend\n\nfunction __fish_openspec_completing_option_value\n set -l current (commandline -ct)\n for option in $argv\n if string match -q -- \"$option=*\" \"$current\"\n return 0\n end\n end\n\n set -l tokens (commandline -opc)\n test (count $tokens) -gt 0; or return 1\n contains -- $tokens[-1] $argv\nend\n\nfunction __fish_openspec_complete_attached_short_path\n set -l option $argv[1]\n set -l current (commandline -ct)\n test \"$current\" != \"$option\"; or return 1\n string match -q -- \"$option*\" \"$current\"; or return 1\n set -l value (string sub -s (math (string length -- \"$option\") + 1) -- \"$current\")\n __fish_complete_path \"$value\"\nend\n\nfunction __fish_openspec_positional_index\n set -l target $argv[1]\n set -l depth $argv[2]\n set -l value_flags $argv[3..]\n set -l tokens (commandline -opc)\n set -e tokens[1]\n set -l count 0\n set -l options 1\n set -l skip 0\n for token in $tokens\n if test $skip -eq 1\n set skip 0\n continue\n end\n if test $options -eq 1\n if test \"$token\" = --\n set options 0\n continue\n end\n if contains -- $token $value_flags\n set skip 1\n continue\n end\n if string match -q -- '-*' $token\n continue\n end\n end\n set count (math $count + 1)\n end\n test $skip -eq 0; or return 1\n test $count -eq (math $target + $depth)\nend";
|
|
6
6
|
export declare const FISH_DYNAMIC_HELPERS = "# Dynamic completion helpers\n\nfunction __fish_openspec_changes\n openspec __complete changes 2>/dev/null | while read -l id desc\n printf '%s\\t%s\\n' \"$id\" \"$desc\"\n end\nend\n\nfunction __fish_openspec_specs\n openspec __complete specs 2>/dev/null | while read -l id desc\n printf '%s\\t%s\\n' \"$id\" \"$desc\"\n end\nend\n\nfunction __fish_openspec_items\n __fish_openspec_changes\n __fish_openspec_specs\nend\n\nfunction __fish_openspec_schemas\n openspec __complete schemas 2>/dev/null | while read -l id desc\n printf '%s\\t%s\\n' \"$id\" \"$desc\"\n end\nend";
|
|
7
7
|
//# sourceMappingURL=fish-templates.d.ts.map
|
|
@@ -2,21 +2,118 @@
|
|
|
2
2
|
* Static template strings for Fish completion scripts.
|
|
3
3
|
* These are Fish-specific helper functions that never change.
|
|
4
4
|
*/
|
|
5
|
-
export const FISH_STATIC_HELPERS = `# Helper function to
|
|
6
|
-
function
|
|
7
|
-
set -l
|
|
8
|
-
set -
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
export const FISH_STATIC_HELPERS = `# Helper function to match the command path at the start of the invocation
|
|
6
|
+
function __fish_openspec_using_command_path
|
|
7
|
+
set -l expected
|
|
8
|
+
set -l value_flags
|
|
9
|
+
set -l reading_value_flags 0
|
|
10
|
+
for argument in $argv
|
|
11
|
+
if test "$argument" = --
|
|
12
|
+
set reading_value_flags 1
|
|
13
|
+
continue
|
|
14
|
+
end
|
|
15
|
+
if test $reading_value_flags -eq 1
|
|
16
|
+
set -a value_flags $argument
|
|
17
|
+
else
|
|
18
|
+
set -a expected $argument
|
|
12
19
|
end
|
|
13
20
|
end
|
|
14
|
-
|
|
21
|
+
|
|
22
|
+
set -l tokens (commandline -opc)
|
|
23
|
+
set -e tokens[1]
|
|
24
|
+
set -l path
|
|
25
|
+
set -l skip 0
|
|
26
|
+
for token in $tokens
|
|
27
|
+
if test $skip -eq 1
|
|
28
|
+
set skip 0
|
|
29
|
+
continue
|
|
30
|
+
end
|
|
31
|
+
if test "$token" = --no-color
|
|
32
|
+
continue
|
|
33
|
+
end
|
|
34
|
+
if contains -- $token $value_flags
|
|
35
|
+
set skip 1
|
|
36
|
+
continue
|
|
37
|
+
end
|
|
38
|
+
if string match -q -- '-*' $token
|
|
39
|
+
continue
|
|
40
|
+
end
|
|
41
|
+
set -a path $token
|
|
42
|
+
if test (count $path) -eq (count $expected)
|
|
43
|
+
break
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
test (count $path) -eq (count $expected); or return 1
|
|
47
|
+
for index in (seq (count $expected))
|
|
48
|
+
test "$path[$index]" = "$expected[$index]"; or return 1
|
|
49
|
+
end
|
|
50
|
+
return 0
|
|
15
51
|
end
|
|
16
52
|
|
|
17
53
|
function __fish_openspec_no_subcommand
|
|
18
|
-
set -l
|
|
19
|
-
|
|
54
|
+
set -l tokens (commandline -opc)
|
|
55
|
+
set -e tokens[1]
|
|
56
|
+
for token in $tokens
|
|
57
|
+
if test "$token" != --no-color
|
|
58
|
+
return 1
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
return 0
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
function __fish_openspec_completing_option_value
|
|
65
|
+
set -l current (commandline -ct)
|
|
66
|
+
for option in $argv
|
|
67
|
+
if string match -q -- "$option=*" "$current"
|
|
68
|
+
return 0
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
set -l tokens (commandline -opc)
|
|
73
|
+
test (count $tokens) -gt 0; or return 1
|
|
74
|
+
contains -- $tokens[-1] $argv
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
function __fish_openspec_complete_attached_short_path
|
|
78
|
+
set -l option $argv[1]
|
|
79
|
+
set -l current (commandline -ct)
|
|
80
|
+
test "$current" != "$option"; or return 1
|
|
81
|
+
string match -q -- "$option*" "$current"; or return 1
|
|
82
|
+
set -l value (string sub -s (math (string length -- "$option") + 1) -- "$current")
|
|
83
|
+
__fish_complete_path "$value"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
function __fish_openspec_positional_index
|
|
87
|
+
set -l target $argv[1]
|
|
88
|
+
set -l depth $argv[2]
|
|
89
|
+
set -l value_flags $argv[3..]
|
|
90
|
+
set -l tokens (commandline -opc)
|
|
91
|
+
set -e tokens[1]
|
|
92
|
+
set -l count 0
|
|
93
|
+
set -l options 1
|
|
94
|
+
set -l skip 0
|
|
95
|
+
for token in $tokens
|
|
96
|
+
if test $skip -eq 1
|
|
97
|
+
set skip 0
|
|
98
|
+
continue
|
|
99
|
+
end
|
|
100
|
+
if test $options -eq 1
|
|
101
|
+
if test "$token" = --
|
|
102
|
+
set options 0
|
|
103
|
+
continue
|
|
104
|
+
end
|
|
105
|
+
if contains -- $token $value_flags
|
|
106
|
+
set skip 1
|
|
107
|
+
continue
|
|
108
|
+
end
|
|
109
|
+
if string match -q -- '-*' $token
|
|
110
|
+
continue
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
set count (math $count + 1)
|
|
114
|
+
end
|
|
115
|
+
test $skip -eq 0; or return 1
|
|
116
|
+
test $count -eq (math $target + $depth)
|
|
20
117
|
end`;
|
|
21
118
|
export const FISH_DYNAMIC_HELPERS = `# Dynamic completion helpers
|
|
22
119
|
|
|
@@ -19,6 +19,10 @@ export interface FlagDefinition {
|
|
|
19
19
|
* Whether the flag takes an argument value
|
|
20
20
|
*/
|
|
21
21
|
takesValue?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Completion type for the flag value.
|
|
24
|
+
*/
|
|
25
|
+
completionType?: PositionalType;
|
|
22
26
|
/**
|
|
23
27
|
* Possible values for the flag (for completion suggestions)
|
|
24
28
|
*/
|
package/dist/core/config.js
CHANGED
|
@@ -19,7 +19,12 @@ export const OPENSPEC_MARKERS = {
|
|
|
19
19
|
};
|
|
20
20
|
export const AI_TOOLS = [
|
|
21
21
|
{ name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer', skillsDir: '.amazonq', requiresIdeRestart: true },
|
|
22
|
-
|
|
22
|
+
// Antigravity moved workspace skills and workflows from `.agent` to the
|
|
23
|
+
// shared `.agents` root in v1.20.5. Detection keys off `.agent` and
|
|
24
|
+
// `.agents/workflows` rather than the bare `.agents` root: that root is
|
|
25
|
+
// shared with Codex, Zed, and the vendor-neutral target, so its presence
|
|
26
|
+
// alone says nothing about Antigravity.
|
|
27
|
+
{ name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity', skillsDir: '.agents', legacySkillsDirs: ['.agent'], detectionPaths: ['.agent', '.agents/workflows'], requiresIdeRestart: true },
|
|
23
28
|
{ name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie', skillsDir: '.augment' },
|
|
24
29
|
{ name: 'Bob Shell', value: 'bob', available: true, successLabel: 'Bob Shell', skillsDir: '.bob' },
|
|
25
30
|
{ name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code', skillsDir: '.claude' },
|
|
@@ -49,6 +54,7 @@ export const AI_TOOLS = [
|
|
|
49
54
|
{ name: 'Oh My Pi', value: 'oh-my-pi', available: true, successLabel: 'Oh My Pi', skillsDir: '.omp' },
|
|
50
55
|
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode', skillsDir: '.opencode' },
|
|
51
56
|
{ name: 'Pi', value: 'pi', available: true, successLabel: 'Pi', skillsDir: '.pi' },
|
|
57
|
+
{ name: 'SourceCraft Code Assistant', value: 'codeassistant', available: true, successLabel: 'SourceCraft Code Assistant', skillsDir: '.codeassistant' },
|
|
52
58
|
{ name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder', requiresIdeRestart: true },
|
|
53
59
|
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code', skillsDir: '.qwen' },
|
|
54
60
|
{ name: 'Rovo Dev CLI', value: 'rovodev', available: true, successLabel: 'Rovo Dev CLI', skillsDir: '.rovodev', detectionPaths: ['.rovodev/skills', '.rovodev'] },
|
package/dist/core/init.d.ts
CHANGED
|
@@ -78,6 +78,7 @@ export declare class InitCommand {
|
|
|
78
78
|
private resolveToolsArg;
|
|
79
79
|
private validateTools;
|
|
80
80
|
private createDirectoryStructure;
|
|
81
|
+
private writeGitkeepFiles;
|
|
81
82
|
/**
|
|
82
83
|
* Generates skill files and slash commands for each selected tool,
|
|
83
84
|
* honoring the configured delivery mode (skills, commands, or both).
|
package/dist/core/init.js
CHANGED
|
@@ -12,6 +12,7 @@ import { createRequire } from 'module';
|
|
|
12
12
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
13
13
|
import { classifyOpenSpecDir, MAX_CONTEXT_SIZE, readProjectConfig, storePointerProblem, } from './project-config.js';
|
|
14
14
|
import { findRepoPlanningRootSync } from './planning-home.js';
|
|
15
|
+
import { ANCHORED_OPENSPEC_DIRS, ensureDirectoryAnchor } from './openspec-root.js';
|
|
15
16
|
import { getSkillReferenceTransformer, getTransformerForTool, usesNaturalLanguageSkillReferences } from '../utils/command-references.js';
|
|
16
17
|
import { AI_TOOLS, OPENSPEC_DIR_NAME, resolveToolIdAlias, } from './config.js';
|
|
17
18
|
import { PALETTE } from './styles/palette.js';
|
|
@@ -19,11 +20,11 @@ import { isInteractive } from '../utils/interactive.js';
|
|
|
19
20
|
import { serializeConfig } from './config-prompts.js';
|
|
20
21
|
import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
|
|
21
22
|
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDeferredGlobalPromptSummary, formatDetectionSummary, getLegacyGlobalPromptMatches, omitGlobalLegacyPromptFiles, pickGlobalLegacyPromptFiles, } from './legacy-cleanup.js';
|
|
22
|
-
import { getToolsWithSkillsDir, getToolStates, getSkillTemplates, getCommandContents, generateSkillContent, hasGlobalSkillTarget, resolveToolSkillsDir, toolSupportsSkills, } from './shared/index.js';
|
|
23
|
+
import { getToolsWithSkillsDir, getToolStates, getSkillTemplates, getCommandContents, generateSkillContent, hasGlobalSkillTarget, resolveToolSkillsDir, toolSupportsSkills, formatIdeRestart, } from './shared/index.js';
|
|
23
24
|
import { getGlobalConfig } from './global-config.js';
|
|
24
25
|
import { getProfileWorkflows, ALL_WORKFLOWS } from './profiles.js';
|
|
25
26
|
import { getAvailableTools } from './available-tools.js';
|
|
26
|
-
import { writeSharedSkillTarget } from './shared-skill-target.js';
|
|
27
|
+
import { resolveSharedSkillWriters, sharedSkillRootOwner, writeSharedSkillTarget, } from './shared-skill-target.js';
|
|
27
28
|
import { migrateIfNeeded, migrateLegacyToolDirs, describeLegacyMigration, keptInPlaceNotice, hasMovableContent, scanInstalledWorkflows as scanInstalledWorkflowsShared } from './migration.js';
|
|
28
29
|
import { resolveCommandSurfaceCapability, resolveCommandInvocation, shouldGenerateCommandsForTool, shouldGenerateSkillsForTool, shouldReconcileCommandFilesForTool, shouldRemoveSkillsForTool, } from './command-surface.js';
|
|
29
30
|
import { writeCopilotCloudFiles, readCopilotCloudOptIn, hasExistingManagedCloudFiles, persistCopilotCloudOptIn, removeCopilotCloudFiles, findUnmanagedCloudFiles, listManagedCloudFiles, } from './github-copilot/cloud-agent.js';
|
|
@@ -526,34 +527,8 @@ export class InitCommand {
|
|
|
526
527
|
return deduped;
|
|
527
528
|
}
|
|
528
529
|
validateTools(toolIds, toolStates, projectPath) {
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
const selectedSharedTargets = sharedAgentsTargets.filter((toolId) => toolIds.includes(toolId));
|
|
532
|
-
// A Codex-rendered tree already serves Zed. Keep it when Zed is added later
|
|
533
|
-
// so Codex users do not lose the `$openspec-*` references they require.
|
|
534
|
-
const preserveConfiguredCodex = selectedSharedTargets.includes('zed') &&
|
|
535
|
-
toolStates.get('codex')?.configured;
|
|
536
|
-
const sharedTargetCandidates = preserveConfiguredCodex
|
|
537
|
-
? [...new Set([...selectedSharedTargets, 'codex'])]
|
|
538
|
-
: selectedSharedTargets;
|
|
539
|
-
const sharedTargetOwner = sharedTargetCandidates.includes('codex')
|
|
540
|
-
? 'codex'
|
|
541
|
-
: selectedSharedTargets.includes('zed')
|
|
542
|
-
? 'zed'
|
|
543
|
-
: selectedSharedTargets[0];
|
|
544
|
-
const firstSharedIndex = toolIds.findIndex((id) => sharedAgentsTargets.includes(id));
|
|
545
|
-
const reconciledToolIds = sharedTargetCandidates.length > 1
|
|
546
|
-
? toolIds.flatMap((toolId, index) => {
|
|
547
|
-
if (!sharedAgentsTargets.includes(toolId))
|
|
548
|
-
return [toolId];
|
|
549
|
-
return index === firstSharedIndex && sharedTargetOwner ? [sharedTargetOwner] : [];
|
|
550
|
-
})
|
|
551
|
-
: toolIds;
|
|
552
|
-
if (reconciledToolIds.length !== toolIds.length ||
|
|
553
|
-
reconciledToolIds.some((toolId, index) => toolId !== toolIds[index])) {
|
|
554
|
-
console.log(chalk.dim(`Codex, Zed, and agents share .agents/skills; writing one tree for ${sharedTargetOwner}.`));
|
|
555
|
-
}
|
|
556
|
-
for (const toolId of reconciledToolIds) {
|
|
530
|
+
const selectedTools = [];
|
|
531
|
+
for (const toolId of toolIds) {
|
|
557
532
|
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
558
533
|
if (!tool) {
|
|
559
534
|
const validToolIds = getToolsWithSkillsDir();
|
|
@@ -563,6 +538,48 @@ export class InitCommand {
|
|
|
563
538
|
const validToolsWithSkills = getToolsWithSkillsDir();
|
|
564
539
|
throw new Error(`Tool '${toolId}' does not support skill generation.\nTools with skill generation support:\n ${validToolsWithSkills.join('\n ')}`);
|
|
565
540
|
}
|
|
541
|
+
selectedTools.push(tool);
|
|
542
|
+
}
|
|
543
|
+
// A selected tool may share its physical skills root with an already
|
|
544
|
+
// configured owner. Include that owner in the refresh without dropping the
|
|
545
|
+
// selected tool: it may still have an independent command surface.
|
|
546
|
+
const generationTools = [...selectedTools];
|
|
547
|
+
const delivery = getGlobalConfig().delivery ?? 'both';
|
|
548
|
+
for (const selected of selectedTools) {
|
|
549
|
+
if (!selected.skillsDir)
|
|
550
|
+
continue;
|
|
551
|
+
const selectedOwner = selected.value === 'codex' ||
|
|
552
|
+
!shouldGenerateSkillsForTool(selected.value, delivery)
|
|
553
|
+
? undefined
|
|
554
|
+
: sharedSkillRootOwner(projectPath, selected.value);
|
|
555
|
+
for (const candidate of AI_TOOLS) {
|
|
556
|
+
if (candidate.skillsDir === selected.skillsDir &&
|
|
557
|
+
toolStates.get(candidate.value)?.configured &&
|
|
558
|
+
candidate.value === selectedOwner &&
|
|
559
|
+
!generationTools.includes(candidate)) {
|
|
560
|
+
generationTools.push(candidate);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const skillWriters = resolveSharedSkillWriters(projectPath, generationTools);
|
|
565
|
+
const sharedRoots = new Map();
|
|
566
|
+
for (const tool of generationTools) {
|
|
567
|
+
if (!tool.skillsDir)
|
|
568
|
+
continue;
|
|
569
|
+
const group = sharedRoots.get(tool.skillsDir) ?? [];
|
|
570
|
+
group.push(tool);
|
|
571
|
+
sharedRoots.set(tool.skillsDir, group);
|
|
572
|
+
}
|
|
573
|
+
for (const [root, group] of sharedRoots) {
|
|
574
|
+
if (group.length < 2)
|
|
575
|
+
continue;
|
|
576
|
+
const owner = group.find((tool) => skillWriters.has(tool.value));
|
|
577
|
+
console.log(chalk.dim(`${group.map((tool) => tool.name).join(', ')} share ${root}/skills; writing one tree for ${owner?.value}.`));
|
|
578
|
+
}
|
|
579
|
+
const validatedTools = [];
|
|
580
|
+
for (const tool of generationTools) {
|
|
581
|
+
if (!toolSupportsSkills(tool))
|
|
582
|
+
continue;
|
|
566
583
|
const preState = toolStates.get(tool.value);
|
|
567
584
|
const skillsPath = resolveToolSkillsDir(projectPath, tool);
|
|
568
585
|
const isGlobalSkillTarget = hasGlobalSkillTarget(tool);
|
|
@@ -574,7 +591,7 @@ export class InitCommand {
|
|
|
574
591
|
skillsRoot: isGlobalSkillTarget ? skillsPath : projectPath,
|
|
575
592
|
isGlobalSkillTarget,
|
|
576
593
|
wasConfigured: preState?.configured ?? false,
|
|
577
|
-
|
|
594
|
+
writesSkills: !tool.skillsDir || skillWriters.has(tool.value),
|
|
578
595
|
});
|
|
579
596
|
}
|
|
580
597
|
return validatedTools;
|
|
@@ -583,36 +600,37 @@ export class InitCommand {
|
|
|
583
600
|
// DIRECTORY STRUCTURE
|
|
584
601
|
// ═══════════════════════════════════════════════════════════
|
|
585
602
|
async createDirectoryStructure(openspecPath, extendMode) {
|
|
603
|
+
const directories = [
|
|
604
|
+
openspecPath,
|
|
605
|
+
path.join(openspecPath, 'specs'),
|
|
606
|
+
path.join(openspecPath, 'changes'),
|
|
607
|
+
path.join(openspecPath, 'changes', 'archive'),
|
|
608
|
+
];
|
|
586
609
|
if (extendMode) {
|
|
587
610
|
// In extend mode, just ensure directories exist without spinner
|
|
588
|
-
const directories = [
|
|
589
|
-
openspecPath,
|
|
590
|
-
path.join(openspecPath, 'specs'),
|
|
591
|
-
path.join(openspecPath, 'changes'),
|
|
592
|
-
path.join(openspecPath, 'changes', 'archive'),
|
|
593
|
-
];
|
|
594
611
|
for (const dir of directories) {
|
|
595
612
|
FileSystemUtils.assertProjectArtifactPath(path.dirname(openspecPath), dir);
|
|
596
613
|
await FileSystemUtils.createDirectory(dir);
|
|
597
614
|
}
|
|
615
|
+
await this.writeGitkeepFiles(openspecPath);
|
|
598
616
|
return;
|
|
599
617
|
}
|
|
600
618
|
const spinner = this.startSpinner('Creating OpenSpec structure...');
|
|
601
|
-
const directories = [
|
|
602
|
-
openspecPath,
|
|
603
|
-
path.join(openspecPath, 'specs'),
|
|
604
|
-
path.join(openspecPath, 'changes'),
|
|
605
|
-
path.join(openspecPath, 'changes', 'archive'),
|
|
606
|
-
];
|
|
607
619
|
for (const dir of directories) {
|
|
608
620
|
FileSystemUtils.assertProjectArtifactPath(path.dirname(openspecPath), dir);
|
|
609
621
|
await FileSystemUtils.createDirectory(dir);
|
|
610
622
|
}
|
|
623
|
+
await this.writeGitkeepFiles(openspecPath);
|
|
611
624
|
spinner.stopAndPersist({
|
|
612
625
|
symbol: PALETTE.white('▌'),
|
|
613
626
|
text: PALETTE.white('OpenSpec structure created'),
|
|
614
627
|
});
|
|
615
628
|
}
|
|
629
|
+
async writeGitkeepFiles(openspecPath) {
|
|
630
|
+
for (const relativeDir of ANCHORED_OPENSPEC_DIRS) {
|
|
631
|
+
await ensureDirectoryAnchor(path.dirname(openspecPath), relativeDir);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
616
634
|
// ═══════════════════════════════════════════════════════════
|
|
617
635
|
// SKILL & COMMAND GENERATION
|
|
618
636
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -648,7 +666,7 @@ export class InitCommand {
|
|
|
648
666
|
const shouldGenerateSkills = shouldGenerateSkillsForTool(tool.value, delivery);
|
|
649
667
|
const shouldGenerateCommands = shouldGenerateCommandsForTool(tool.value, delivery);
|
|
650
668
|
// Generate skill files if the selected delivery and tool capability allow skills
|
|
651
|
-
if (shouldGenerateSkills) {
|
|
669
|
+
if (shouldGenerateSkills && tool.writesSkills) {
|
|
652
670
|
// Create skill directories and SKILL.md files
|
|
653
671
|
for (const { template, dirName } of skillTemplates) {
|
|
654
672
|
const skillDir = path.join(tool.skillsPath, dirName);
|
|
@@ -662,7 +680,9 @@ export class InitCommand {
|
|
|
662
680
|
}
|
|
663
681
|
writeSharedSkillTarget(projectPath, tool.value);
|
|
664
682
|
}
|
|
665
|
-
if (shouldRemoveSkillsForTool(tool.value, delivery) &&
|
|
683
|
+
if (shouldRemoveSkillsForTool(tool.value, delivery) &&
|
|
684
|
+
tool.writesSkills &&
|
|
685
|
+
!tool.isGlobalSkillTarget) {
|
|
666
686
|
removedSkillCount += await this.removeSkillDirs(tool.skillsRoot, tool.skillsPath);
|
|
667
687
|
// Retain an explicit selection even when this delivery mode produces
|
|
668
688
|
// no skills, so a divergent legacy sibling cannot reclaim ownership.
|
|
@@ -1015,27 +1035,13 @@ export class InitCommand {
|
|
|
1015
1035
|
console.log();
|
|
1016
1036
|
console.log(`Learn more: ${chalk.cyan('https://github.com/Fission-AI/OpenSpec')}`);
|
|
1017
1037
|
console.log(`Feedback: ${chalk.cyan('https://github.com/Fission-AI/OpenSpec/issues')}`);
|
|
1018
|
-
// Restart instruction
|
|
1019
|
-
//
|
|
1020
|
-
//
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
// under the active delivery (an IDE tool that generated nothing has nothing a
|
|
1024
|
-
// restart would pick up, even if a co-configured CLI tool did generate).
|
|
1025
|
-
// Wording follows what the IDE tool itself generated, not the global
|
|
1026
|
-
// aggregate: it must not say "commands" when the IDE tool only got skills
|
|
1027
|
-
// while a co-configured CLI tool got commands. Not "slash commands" either:
|
|
1028
|
-
// Amazon Q's generated files are prompt-library entries invoked with @, so a
|
|
1029
|
-
// restart line promising slash commands would be wrong for it.
|
|
1030
|
-
const restartCommandsGenerated = successfulTools.some((tool) => tool.requiresIdeRestart &&
|
|
1031
|
-
shouldGenerateCommandsForTool(tool.value, activeDelivery));
|
|
1032
|
-
const restartSkillsGenerated = successfulTools.some((tool) => tool.requiresIdeRestart &&
|
|
1033
|
-
shouldGenerateSkillsForTool(tool.value, activeDelivery));
|
|
1034
|
-
if (restartCommandsGenerated || restartSkillsGenerated) {
|
|
1038
|
+
// Restart instruction for successfully configured IDE/editor-resident tools
|
|
1039
|
+
// with a supported surface under the active delivery. The rule and wording live in
|
|
1040
|
+
// formatIdeRestart so `update` says the same thing for the same event.
|
|
1041
|
+
const restartHint = formatIdeRestart(successfulTools.map((tool) => tool.value), activeDelivery);
|
|
1042
|
+
if (restartHint) {
|
|
1035
1043
|
console.log();
|
|
1036
|
-
console.log(chalk.white(
|
|
1037
|
-
? 'Restart your IDE for the new commands to take effect.'
|
|
1038
|
-
: 'Restart your IDE for the new skills to take effect.'));
|
|
1044
|
+
console.log(chalk.white(restartHint));
|
|
1039
1045
|
}
|
|
1040
1046
|
console.log();
|
|
1041
1047
|
}
|