@fission-ai/openspec 0.18.0 → 0.19.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.
Files changed (44) hide show
  1. package/README.md +52 -0
  2. package/dist/cli/index.js +32 -2
  3. package/dist/commands/artifact-workflow.js +6 -1
  4. package/dist/commands/completion.js +42 -6
  5. package/dist/core/completions/command-registry.js +7 -1
  6. package/dist/core/completions/factory.d.ts +15 -2
  7. package/dist/core/completions/factory.js +19 -1
  8. package/dist/core/completions/generators/bash-generator.d.ts +32 -0
  9. package/dist/core/completions/generators/bash-generator.js +174 -0
  10. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  11. package/dist/core/completions/generators/fish-generator.js +157 -0
  12. package/dist/core/completions/generators/powershell-generator.d.ts +32 -0
  13. package/dist/core/completions/generators/powershell-generator.js +198 -0
  14. package/dist/core/completions/generators/zsh-generator.d.ts +0 -14
  15. package/dist/core/completions/generators/zsh-generator.js +55 -124
  16. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  17. package/dist/core/completions/installers/bash-installer.js +318 -0
  18. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  19. package/dist/core/completions/installers/fish-installer.js +143 -0
  20. package/dist/core/completions/installers/powershell-installer.d.ts +88 -0
  21. package/dist/core/completions/installers/powershell-installer.js +327 -0
  22. package/dist/core/completions/installers/zsh-installer.d.ts +1 -12
  23. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  24. package/dist/core/completions/templates/bash-templates.js +24 -0
  25. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  26. package/dist/core/completions/templates/fish-templates.js +39 -0
  27. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  28. package/dist/core/completions/templates/powershell-templates.js +25 -0
  29. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  30. package/dist/core/completions/templates/zsh-templates.js +36 -0
  31. package/dist/core/config.js +1 -0
  32. package/dist/core/configurators/slash/codebuddy.js +6 -9
  33. package/dist/core/configurators/slash/continue.d.ts +9 -0
  34. package/dist/core/configurators/slash/continue.js +46 -0
  35. package/dist/core/configurators/slash/registry.js +3 -0
  36. package/dist/core/templates/skill-templates.d.ts +10 -0
  37. package/dist/core/templates/skill-templates.js +482 -20
  38. package/dist/telemetry/config.d.ts +32 -0
  39. package/dist/telemetry/config.js +68 -0
  40. package/dist/telemetry/index.d.ts +31 -0
  41. package/dist/telemetry/index.js +145 -0
  42. package/dist/utils/file-system.d.ts +6 -0
  43. package/dist/utils/file-system.js +43 -2
  44. package/package.json +2 -1
@@ -0,0 +1,157 @@
1
+ import { FISH_STATIC_HELPERS, FISH_DYNAMIC_HELPERS } from '../templates/fish-templates.js';
2
+ /**
3
+ * Generates Fish completion scripts for the OpenSpec CLI.
4
+ * Follows Fish completion conventions using the complete command.
5
+ */
6
+ export class FishGenerator {
7
+ shell = 'fish';
8
+ /**
9
+ * Generate a Fish completion script
10
+ *
11
+ * @param commands - Command definitions to generate completions for
12
+ * @returns Fish completion script as a string
13
+ */
14
+ generate(commands) {
15
+ // Build top-level commands using push() for loop clarity
16
+ const topLevelLines = [];
17
+ for (const cmd of commands) {
18
+ topLevelLines.push(`# ${cmd.name} command`);
19
+ topLevelLines.push(`complete -c openspec -n '__fish_openspec_no_subcommand' -a '${cmd.name}' -d '${this.escapeDescription(cmd.description)}'`);
20
+ }
21
+ const topLevelCommands = topLevelLines.join('\n');
22
+ // Build command-specific completions using push() for loop clarity
23
+ const commandCompletionLines = [];
24
+ for (const cmd of commands) {
25
+ commandCompletionLines.push(...this.generateCommandCompletions(cmd));
26
+ commandCompletionLines.push('');
27
+ }
28
+ const commandCompletions = commandCompletionLines.join('\n');
29
+ // Static helper functions from template
30
+ const helperFunctions = FISH_STATIC_HELPERS;
31
+ // Dynamic completion helpers from template
32
+ const dynamicHelpers = FISH_DYNAMIC_HELPERS;
33
+ // Assemble final script with template literal
34
+ return `# Fish completion script for OpenSpec CLI
35
+ # Auto-generated - do not edit manually
36
+
37
+ ${helperFunctions}
38
+ ${dynamicHelpers}
39
+ ${topLevelCommands}
40
+
41
+ ${commandCompletions}`;
42
+ }
43
+ /**
44
+ * Generate completions for a specific command
45
+ */
46
+ generateCommandCompletions(cmd) {
47
+ const lines = [];
48
+ // If command has subcommands
49
+ if (cmd.subcommands && cmd.subcommands.length > 0) {
50
+ // Add subcommand completions
51
+ for (const subcmd of cmd.subcommands) {
52
+ lines.push(`complete -c openspec -n '__fish_openspec_using_subcommand ${cmd.name}; and not __fish_openspec_using_subcommand ${subcmd.name}' -a '${subcmd.name}' -d '${this.escapeDescription(subcmd.description)}'`);
53
+ }
54
+ lines.push('');
55
+ // Add flags for parent command
56
+ for (const flag of cmd.flags) {
57
+ lines.push(...this.generateFlagCompletion(flag, `__fish_openspec_using_subcommand ${cmd.name}`));
58
+ }
59
+ // Add completions for each subcommand
60
+ for (const subcmd of cmd.subcommands) {
61
+ lines.push(`# ${cmd.name} ${subcmd.name} flags`);
62
+ for (const flag of subcmd.flags) {
63
+ lines.push(...this.generateFlagCompletion(flag, `__fish_openspec_using_subcommand ${cmd.name}; and __fish_openspec_using_subcommand ${subcmd.name}`));
64
+ }
65
+ // Add positional completions for subcommand
66
+ if (subcmd.acceptsPositional) {
67
+ lines.push(...this.generatePositionalCompletion(subcmd.positionalType, `__fish_openspec_using_subcommand ${cmd.name}; and __fish_openspec_using_subcommand ${subcmd.name}`));
68
+ }
69
+ }
70
+ }
71
+ else {
72
+ // Command without subcommands
73
+ lines.push(`# ${cmd.name} flags`);
74
+ for (const flag of cmd.flags) {
75
+ lines.push(...this.generateFlagCompletion(flag, `__fish_openspec_using_subcommand ${cmd.name}`));
76
+ }
77
+ // Add positional completions
78
+ if (cmd.acceptsPositional) {
79
+ lines.push(...this.generatePositionalCompletion(cmd.positionalType, `__fish_openspec_using_subcommand ${cmd.name}`));
80
+ }
81
+ }
82
+ return lines;
83
+ }
84
+ /**
85
+ * Generate flag completion
86
+ */
87
+ generateFlagCompletion(flag, condition) {
88
+ const lines = [];
89
+ const longFlag = `--${flag.name}`;
90
+ const shortFlag = flag.short ? `-${flag.short}` : undefined;
91
+ if (flag.takesValue && flag.values) {
92
+ // Flag with enum values
93
+ for (const value of flag.values) {
94
+ if (shortFlag) {
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
+ }
100
+ }
101
+ }
102
+ else if (flag.takesValue) {
103
+ // Flag that takes a value but no specific values defined
104
+ if (shortFlag) {
105
+ lines.push(`complete -c openspec -n '${condition}' -s ${flag.short} -l ${flag.name} -r -d '${this.escapeDescription(flag.description)}'`);
106
+ }
107
+ else {
108
+ lines.push(`complete -c openspec -n '${condition}' -l ${flag.name} -r -d '${this.escapeDescription(flag.description)}'`);
109
+ }
110
+ }
111
+ else {
112
+ // Boolean flag
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
+ }
119
+ }
120
+ return lines;
121
+ }
122
+ /**
123
+ * Generate positional argument completion
124
+ */
125
+ generatePositionalCompletion(positionalType, condition) {
126
+ const lines = [];
127
+ switch (positionalType) {
128
+ case 'change-id':
129
+ lines.push(`complete -c openspec -n '${condition}' -a '(__fish_openspec_changes)' -f`);
130
+ break;
131
+ case 'spec-id':
132
+ lines.push(`complete -c openspec -n '${condition}' -a '(__fish_openspec_specs)' -f`);
133
+ break;
134
+ case 'change-or-spec-id':
135
+ lines.push(`complete -c openspec -n '${condition}' -a '(__fish_openspec_items)' -f`);
136
+ break;
137
+ case 'shell':
138
+ lines.push(`complete -c openspec -n '${condition}' -a 'zsh bash fish powershell' -f`);
139
+ break;
140
+ case 'path':
141
+ // Fish automatically completes files, no need to specify
142
+ break;
143
+ }
144
+ return lines;
145
+ }
146
+ /**
147
+ * Escape description text for Fish
148
+ */
149
+ escapeDescription(description) {
150
+ return description
151
+ .replace(/\\/g, '\\\\') // Backslashes first
152
+ .replace(/'/g, "\\'") // Single quotes
153
+ .replace(/\$/g, '\\$') // Dollar signs (prevents $())
154
+ .replace(/`/g, '\\`'); // Backticks
155
+ }
156
+ }
157
+ //# sourceMappingURL=fish-generator.js.map
@@ -0,0 +1,32 @@
1
+ import { CompletionGenerator, CommandDefinition } from '../types.js';
2
+ /**
3
+ * Generates PowerShell completion scripts for the OpenSpec CLI.
4
+ * Uses Register-ArgumentCompleter for command completion.
5
+ */
6
+ export declare class PowerShellGenerator implements CompletionGenerator {
7
+ readonly shell: "powershell";
8
+ /**
9
+ * Generate a PowerShell completion script
10
+ *
11
+ * @param commands - Command definitions to generate completions for
12
+ * @returns PowerShell completion script as a string
13
+ */
14
+ generate(commands: CommandDefinition[]): string;
15
+ /**
16
+ * Generate completion case for a command
17
+ */
18
+ private generateCommandCase;
19
+ /**
20
+ * Generate argument completion (flags and positional)
21
+ */
22
+ private generateArgumentCompletion;
23
+ /**
24
+ * Generate positional argument completion
25
+ */
26
+ private generatePositionalCompletion;
27
+ /**
28
+ * Escape description text for PowerShell
29
+ */
30
+ private escapeDescription;
31
+ }
32
+ //# sourceMappingURL=powershell-generator.d.ts.map
@@ -0,0 +1,198 @@
1
+ import { POWERSHELL_DYNAMIC_HELPERS } from '../templates/powershell-templates.js';
2
+ /**
3
+ * Generates PowerShell completion scripts for the OpenSpec CLI.
4
+ * Uses Register-ArgumentCompleter for command completion.
5
+ */
6
+ export class PowerShellGenerator {
7
+ shell = 'powershell';
8
+ /**
9
+ * Generate a PowerShell completion script
10
+ *
11
+ * @param commands - Command definitions to generate completions for
12
+ * @returns PowerShell completion script as a string
13
+ */
14
+ generate(commands) {
15
+ // Build top-level commands using push() for loop clarity
16
+ const commandLines = [];
17
+ for (const cmd of commands) {
18
+ commandLines.push(` @{Name="${cmd.name}"; Description="${this.escapeDescription(cmd.description)}"},`);
19
+ }
20
+ const topLevelCommands = commandLines.join('\n');
21
+ // Build command cases using push() for loop clarity
22
+ const commandCaseLines = [];
23
+ for (const cmd of commands) {
24
+ commandCaseLines.push(` "${cmd.name}" {`);
25
+ commandCaseLines.push(...this.generateCommandCase(cmd, ' '));
26
+ commandCaseLines.push(' }');
27
+ }
28
+ const commandCases = commandCaseLines.join('\n');
29
+ // Dynamic completion helpers from template
30
+ const helpers = POWERSHELL_DYNAMIC_HELPERS;
31
+ // Assemble final script with template literal
32
+ return `# PowerShell completion script for OpenSpec CLI
33
+ # Auto-generated - do not edit manually
34
+
35
+ ${helpers}
36
+ $openspecCompleter = {
37
+ param($wordToComplete, $commandAst, $cursorPosition)
38
+
39
+ $tokens = $commandAst.ToString() -split "\\s+"
40
+ $commandCount = ($tokens | Measure-Object).Count
41
+
42
+ # Top-level commands
43
+ if ($commandCount -eq 1 -or ($commandCount -eq 2 -and $wordToComplete)) {
44
+ $commands = @(
45
+ ${topLevelCommands}
46
+ )
47
+ $commands | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {
48
+ [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, "ParameterValue", $_.Description)
49
+ }
50
+ return
51
+ }
52
+
53
+ $command = $tokens[1]
54
+
55
+ switch ($command) {
56
+ ${commandCases}
57
+ }
58
+ }
59
+
60
+ Register-ArgumentCompleter -CommandName openspec -ScriptBlock $openspecCompleter
61
+ `;
62
+ }
63
+ /**
64
+ * Generate completion case for a command
65
+ */
66
+ generateCommandCase(cmd, indent) {
67
+ const lines = [];
68
+ if (cmd.subcommands && cmd.subcommands.length > 0) {
69
+ // First, check if user is typing a flag for the parent command
70
+ if (cmd.flags.length > 0) {
71
+ lines.push(`${indent}if ($wordToComplete -like "-*") {`);
72
+ lines.push(`${indent} $flags = @(`);
73
+ for (const flag of cmd.flags) {
74
+ const longFlag = `--${flag.name}`;
75
+ const shortFlag = flag.short ? `-${flag.short}` : undefined;
76
+ if (shortFlag) {
77
+ lines.push(`${indent} @{Name="${longFlag}"; Description="${this.escapeDescription(flag.description)}"},`);
78
+ lines.push(`${indent} @{Name="${shortFlag}"; Description="${this.escapeDescription(flag.description)}"},`);
79
+ }
80
+ else {
81
+ lines.push(`${indent} @{Name="${longFlag}"; Description="${this.escapeDescription(flag.description)}"},`);
82
+ }
83
+ }
84
+ lines.push(`${indent} )`);
85
+ lines.push(`${indent} $flags | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {`);
86
+ lines.push(`${indent} [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, "ParameterName", $_.Description)`);
87
+ lines.push(`${indent} }`);
88
+ lines.push(`${indent} return`);
89
+ lines.push(`${indent}}`);
90
+ lines.push('');
91
+ }
92
+ // Handle subcommands
93
+ lines.push(`${indent}if ($commandCount -eq 2 -or ($commandCount -eq 3 -and $wordToComplete)) {`);
94
+ lines.push(`${indent} $subcommands = @(`);
95
+ for (const subcmd of cmd.subcommands) {
96
+ lines.push(`${indent} @{Name="${subcmd.name}"; Description="${this.escapeDescription(subcmd.description)}"},`);
97
+ }
98
+ lines.push(`${indent} )`);
99
+ lines.push(`${indent} $subcommands | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {`);
100
+ lines.push(`${indent} [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, "ParameterValue", $_.Description)`);
101
+ lines.push(`${indent} }`);
102
+ lines.push(`${indent} return`);
103
+ lines.push(`${indent}}`);
104
+ lines.push('');
105
+ lines.push(`${indent}$subcommand = if ($commandCount -gt 2) { $tokens[2] } else { "" }`);
106
+ lines.push(`${indent}switch ($subcommand) {`);
107
+ for (const subcmd of cmd.subcommands) {
108
+ lines.push(`${indent} "${subcmd.name}" {`);
109
+ lines.push(...this.generateArgumentCompletion(subcmd, indent + ' '));
110
+ lines.push(`${indent} }`);
111
+ }
112
+ lines.push(`${indent}}`);
113
+ }
114
+ else {
115
+ // No subcommands
116
+ lines.push(...this.generateArgumentCompletion(cmd, indent));
117
+ }
118
+ return lines;
119
+ }
120
+ /**
121
+ * Generate argument completion (flags and positional)
122
+ */
123
+ generateArgumentCompletion(cmd, indent) {
124
+ const lines = [];
125
+ // Flag completion
126
+ if (cmd.flags.length > 0) {
127
+ lines.push(`${indent}if ($wordToComplete -like "-*") {`);
128
+ lines.push(`${indent} $flags = @(`);
129
+ for (const flag of cmd.flags) {
130
+ const longFlag = `--${flag.name}`;
131
+ const shortFlag = flag.short ? `-${flag.short}` : undefined;
132
+ if (shortFlag) {
133
+ lines.push(`${indent} @{Name="${longFlag}"; Description="${this.escapeDescription(flag.description)}"},`);
134
+ lines.push(`${indent} @{Name="${shortFlag}"; Description="${this.escapeDescription(flag.description)}"},`);
135
+ }
136
+ else {
137
+ lines.push(`${indent} @{Name="${longFlag}"; Description="${this.escapeDescription(flag.description)}"},`);
138
+ }
139
+ }
140
+ lines.push(`${indent} )`);
141
+ lines.push(`${indent} $flags | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {`);
142
+ lines.push(`${indent} [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, "ParameterName", $_.Description)`);
143
+ lines.push(`${indent} }`);
144
+ lines.push(`${indent} return`);
145
+ lines.push(`${indent}}`);
146
+ lines.push('');
147
+ }
148
+ // Positional completion
149
+ if (cmd.acceptsPositional) {
150
+ lines.push(...this.generatePositionalCompletion(cmd.positionalType, indent));
151
+ }
152
+ return lines;
153
+ }
154
+ /**
155
+ * Generate positional argument completion
156
+ */
157
+ generatePositionalCompletion(positionalType, indent) {
158
+ const lines = [];
159
+ switch (positionalType) {
160
+ case 'change-id':
161
+ lines.push(`${indent}Get-OpenSpecChanges | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {`);
162
+ lines.push(`${indent} [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", "Change: $_")`);
163
+ lines.push(`${indent}}`);
164
+ break;
165
+ case 'spec-id':
166
+ lines.push(`${indent}Get-OpenSpecSpecs | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {`);
167
+ lines.push(`${indent} [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", "Spec: $_")`);
168
+ lines.push(`${indent}}`);
169
+ break;
170
+ case 'change-or-spec-id':
171
+ lines.push(`${indent}$items = @(Get-OpenSpecChanges) + @(Get-OpenSpecSpecs)`);
172
+ lines.push(`${indent}$items | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {`);
173
+ lines.push(`${indent} [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_)`);
174
+ lines.push(`${indent}}`);
175
+ break;
176
+ case 'shell':
177
+ lines.push(`${indent}$shells = @("zsh", "bash", "fish", "powershell")`);
178
+ lines.push(`${indent}$shells | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {`);
179
+ lines.push(`${indent} [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", "Shell: $_")`);
180
+ lines.push(`${indent}}`);
181
+ break;
182
+ case 'path':
183
+ // PowerShell handles file path completion automatically
184
+ break;
185
+ }
186
+ return lines;
187
+ }
188
+ /**
189
+ * Escape description text for PowerShell
190
+ */
191
+ escapeDescription(description) {
192
+ return description
193
+ .replace(/`/g, '``') // Backticks (escape sequences)
194
+ .replace(/\$/g, '`$') // Dollar signs (prevents $())
195
+ .replace(/"/g, '""'); // Double quotes
196
+ }
197
+ }
198
+ //# sourceMappingURL=powershell-generator.js.map
@@ -12,20 +12,6 @@ export declare class ZshGenerator implements CompletionGenerator {
12
12
  * @returns Zsh completion script as a string
13
13
  */
14
14
  generate(commands: CommandDefinition[]): string;
15
- /**
16
- * Generate a single completion function
17
- *
18
- * @param functionName - Name of the completion function
19
- * @param varName - Name of the local array variable
20
- * @param varLabel - Label for the completion items
21
- * @param commandLines - Command line(s) to populate the array
22
- * @param comment - Optional comment describing the function
23
- */
24
- private generateCompletionFunction;
25
- /**
26
- * Generate dynamic completion helper functions for change and spec IDs
27
- */
28
- private generateDynamicCompletionHelpers;
29
15
  /**
30
16
  * Generate completion function for a specific command
31
17
  */
@@ -1,3 +1,4 @@
1
+ import { ZSH_DYNAMIC_HELPERS } from '../templates/zsh-templates.js';
1
2
  /**
2
3
  * Generates Zsh completion scripts for the OpenSpec CLI.
3
4
  * Follows Zsh completion system conventions using the _openspec function.
@@ -11,135 +12,65 @@ export class ZshGenerator {
11
12
  * @returns Zsh completion script as a string
12
13
  */
13
14
  generate(commands) {
14
- const script = [];
15
- // Header comment
16
- script.push('#compdef openspec');
17
- script.push('');
18
- script.push('# Zsh completion script for OpenSpec CLI');
19
- script.push('# Auto-generated - do not edit manually');
20
- script.push('');
21
- // Main completion function
22
- script.push('_openspec() {');
23
- script.push(' local context state line');
24
- script.push(' typeset -A opt_args');
25
- script.push('');
26
- // Generate main command argument specification
27
- script.push(' local -a commands');
28
- script.push(' commands=(');
15
+ // Build command list using push() for loop clarity
16
+ const commandLines = [];
29
17
  for (const cmd of commands) {
30
18
  const escapedDesc = this.escapeDescription(cmd.description);
31
- script.push(` '${cmd.name}:${escapedDesc}'`);
19
+ commandLines.push(` '${cmd.name}:${escapedDesc}'`);
32
20
  }
33
- script.push(' )');
34
- script.push('');
35
- // Main _arguments call
36
- script.push(' _arguments -C \\');
37
- script.push(' "1: :->command" \\');
38
- script.push(' "*::arg:->args"');
39
- script.push('');
40
- // Command dispatch logic
41
- script.push(' case $state in');
42
- script.push(' command)');
43
- script.push(' _describe "openspec command" commands');
44
- script.push(' ;;');
45
- script.push(' args)');
46
- script.push(' case $words[1] in');
47
- // Generate completion for each command
21
+ const commandList = commandLines.join('\n');
22
+ // Build command cases using push() for loop clarity
23
+ const commandCaseLines = [];
48
24
  for (const cmd of commands) {
49
- script.push(` ${cmd.name})`);
50
- script.push(` _openspec_${this.sanitizeFunctionName(cmd.name)}`);
51
- script.push(' ;;');
25
+ commandCaseLines.push(` ${cmd.name})`);
26
+ commandCaseLines.push(` _openspec_${this.sanitizeFunctionName(cmd.name)}`);
27
+ commandCaseLines.push(' ;;');
52
28
  }
53
- script.push(' esac');
54
- script.push(' ;;');
55
- script.push(' esac');
56
- script.push('}');
57
- script.push('');
58
- // Generate individual command completion functions
29
+ const commandCases = commandCaseLines.join('\n');
30
+ // Build command functions using push() for loop clarity
31
+ const commandFunctionLines = [];
59
32
  for (const cmd of commands) {
60
- script.push(...this.generateCommandFunction(cmd));
61
- script.push('');
62
- }
63
- // Add dynamic completion helper functions
64
- script.push(...this.generateDynamicCompletionHelpers());
65
- // Register the completion function
66
- script.push('compdef _openspec openspec');
67
- script.push('');
68
- return script.join('\n');
69
- }
70
- /**
71
- * Generate a single completion function
72
- *
73
- * @param functionName - Name of the completion function
74
- * @param varName - Name of the local array variable
75
- * @param varLabel - Label for the completion items
76
- * @param commandLines - Command line(s) to populate the array
77
- * @param comment - Optional comment describing the function
78
- */
79
- generateCompletionFunction(functionName, varName, varLabel, commandLines, comment) {
80
- const lines = [];
81
- if (comment) {
82
- lines.push(comment);
83
- }
84
- lines.push(`${functionName}() {`);
85
- lines.push(` local -a ${varName}`);
86
- if (commandLines.length === 1) {
87
- lines.push(` ${commandLines[0]}`);
88
- }
89
- else {
90
- lines.push(` ${varName}=(`);
91
- for (let i = 0; i < commandLines.length; i++) {
92
- const suffix = i < commandLines.length - 1 ? ' \\' : '';
93
- lines.push(` ${commandLines[i]}${suffix}`);
94
- }
95
- lines.push(' )');
96
- }
97
- lines.push(` _describe "${varLabel}" ${varName}`);
98
- lines.push('}');
99
- lines.push('');
100
- return lines;
101
- }
102
- /**
103
- * Generate dynamic completion helper functions for change and spec IDs
104
- */
105
- generateDynamicCompletionHelpers() {
106
- const lines = [];
107
- lines.push('# Dynamic completion helpers');
108
- lines.push('');
109
- // Helper function for completing change IDs
110
- lines.push('# Use openspec __complete to get available changes');
111
- lines.push('_openspec_complete_changes() {');
112
- lines.push(' local -a changes');
113
- lines.push(' while IFS=$\'\\t\' read -r id desc; do');
114
- lines.push(' changes+=("$id:$desc")');
115
- lines.push(' done < <(openspec __complete changes 2>/dev/null)');
116
- lines.push(' _describe "change" changes');
117
- lines.push('}');
118
- lines.push('');
119
- // Helper function for completing spec IDs
120
- lines.push('# Use openspec __complete to get available specs');
121
- lines.push('_openspec_complete_specs() {');
122
- lines.push(' local -a specs');
123
- lines.push(' while IFS=$\'\\t\' read -r id desc; do');
124
- lines.push(' specs+=("$id:$desc")');
125
- lines.push(' done < <(openspec __complete specs 2>/dev/null)');
126
- lines.push(' _describe "spec" specs');
127
- lines.push('}');
128
- lines.push('');
129
- // Helper function for completing both changes and specs
130
- lines.push('# Get both changes and specs');
131
- lines.push('_openspec_complete_items() {');
132
- lines.push(' local -a items');
133
- lines.push(' while IFS=$\'\\t\' read -r id desc; do');
134
- lines.push(' items+=("$id:$desc")');
135
- lines.push(' done < <(openspec __complete changes 2>/dev/null)');
136
- lines.push(' while IFS=$\'\\t\' read -r id desc; do');
137
- lines.push(' items+=("$id:$desc")');
138
- lines.push(' done < <(openspec __complete specs 2>/dev/null)');
139
- lines.push(' _describe "item" items');
140
- lines.push('}');
141
- lines.push('');
142
- return lines;
33
+ commandFunctionLines.push(...this.generateCommandFunction(cmd));
34
+ commandFunctionLines.push('');
35
+ }
36
+ const commandFunctions = commandFunctionLines.join('\n');
37
+ // Dynamic completion helpers from template
38
+ const helpers = ZSH_DYNAMIC_HELPERS;
39
+ // Assemble final script with template literal
40
+ return `#compdef openspec
41
+
42
+ # Zsh completion script for OpenSpec CLI
43
+ # Auto-generated - do not edit manually
44
+
45
+ _openspec() {
46
+ local context state line
47
+ typeset -A opt_args
48
+
49
+ local -a commands
50
+ commands=(
51
+ ${commandList}
52
+ )
53
+
54
+ _arguments -C \\
55
+ "1: :->command" \\
56
+ "*::arg:->args"
57
+
58
+ case $state in
59
+ command)
60
+ _describe "openspec command" commands
61
+ ;;
62
+ args)
63
+ case $words[1] in
64
+ ${commandCases}
65
+ esac
66
+ ;;
67
+ esac
68
+ }
69
+
70
+ ${commandFunctions}
71
+ ${helpers}
72
+ compdef _openspec openspec
73
+ `;
143
74
  }
144
75
  /**
145
76
  * Generate completion function for a specific command
@@ -284,7 +215,7 @@ export class ZshGenerator {
284
215
  case 'path':
285
216
  return "'*:path:_files'";
286
217
  case 'shell':
287
- return "'*:shell:(zsh)'";
218
+ return "'*:shell:(zsh bash fish powershell)'";
288
219
  default:
289
220
  return "'*: :_default'";
290
221
  }