@fission-ai/openspec 0.17.2 → 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 (89) hide show
  1. package/README.md +52 -0
  2. package/dist/cli/index.js +39 -3
  3. package/dist/commands/artifact-workflow.d.ts +17 -0
  4. package/dist/commands/artifact-workflow.js +823 -0
  5. package/dist/commands/completion.js +42 -6
  6. package/dist/core/archive.d.ts +0 -5
  7. package/dist/core/archive.js +4 -257
  8. package/dist/core/artifact-graph/graph.d.ts +56 -0
  9. package/dist/core/artifact-graph/graph.js +141 -0
  10. package/dist/core/artifact-graph/index.d.ts +7 -0
  11. package/dist/core/artifact-graph/index.js +13 -0
  12. package/dist/core/artifact-graph/instruction-loader.d.ts +130 -0
  13. package/dist/core/artifact-graph/instruction-loader.js +173 -0
  14. package/dist/core/artifact-graph/resolver.d.ts +61 -0
  15. package/dist/core/artifact-graph/resolver.js +187 -0
  16. package/dist/core/artifact-graph/schema.d.ts +13 -0
  17. package/dist/core/artifact-graph/schema.js +108 -0
  18. package/dist/core/artifact-graph/state.d.ts +12 -0
  19. package/dist/core/artifact-graph/state.js +54 -0
  20. package/dist/core/artifact-graph/types.d.ts +45 -0
  21. package/dist/core/artifact-graph/types.js +43 -0
  22. package/dist/core/completions/command-registry.js +7 -1
  23. package/dist/core/completions/factory.d.ts +15 -2
  24. package/dist/core/completions/factory.js +19 -1
  25. package/dist/core/completions/generators/bash-generator.d.ts +32 -0
  26. package/dist/core/completions/generators/bash-generator.js +174 -0
  27. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  28. package/dist/core/completions/generators/fish-generator.js +157 -0
  29. package/dist/core/completions/generators/powershell-generator.d.ts +32 -0
  30. package/dist/core/completions/generators/powershell-generator.js +198 -0
  31. package/dist/core/completions/generators/zsh-generator.d.ts +0 -14
  32. package/dist/core/completions/generators/zsh-generator.js +55 -124
  33. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  34. package/dist/core/completions/installers/bash-installer.js +318 -0
  35. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  36. package/dist/core/completions/installers/fish-installer.js +143 -0
  37. package/dist/core/completions/installers/powershell-installer.d.ts +88 -0
  38. package/dist/core/completions/installers/powershell-installer.js +327 -0
  39. package/dist/core/completions/installers/zsh-installer.d.ts +1 -12
  40. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  41. package/dist/core/completions/templates/bash-templates.js +24 -0
  42. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  43. package/dist/core/completions/templates/fish-templates.js +39 -0
  44. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  45. package/dist/core/completions/templates/powershell-templates.js +25 -0
  46. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  47. package/dist/core/completions/templates/zsh-templates.js +36 -0
  48. package/dist/core/config.js +1 -0
  49. package/dist/core/configurators/slash/codebuddy.js +6 -9
  50. package/dist/core/configurators/slash/continue.d.ts +9 -0
  51. package/dist/core/configurators/slash/continue.js +46 -0
  52. package/dist/core/configurators/slash/registry.js +3 -0
  53. package/dist/core/converters/json-converter.js +2 -1
  54. package/dist/core/global-config.d.ts +10 -0
  55. package/dist/core/global-config.js +28 -0
  56. package/dist/core/index.d.ts +1 -1
  57. package/dist/core/index.js +1 -1
  58. package/dist/core/list.d.ts +6 -1
  59. package/dist/core/list.js +88 -6
  60. package/dist/core/specs-apply.d.ts +73 -0
  61. package/dist/core/specs-apply.js +384 -0
  62. package/dist/core/templates/skill-templates.d.ts +86 -0
  63. package/dist/core/templates/skill-templates.js +1934 -0
  64. package/dist/core/update.js +1 -1
  65. package/dist/core/validation/validator.js +2 -1
  66. package/dist/core/view.js +28 -8
  67. package/dist/telemetry/config.d.ts +32 -0
  68. package/dist/telemetry/config.js +68 -0
  69. package/dist/telemetry/index.d.ts +31 -0
  70. package/dist/telemetry/index.js +145 -0
  71. package/dist/utils/change-metadata.d.ts +47 -0
  72. package/dist/utils/change-metadata.js +130 -0
  73. package/dist/utils/change-utils.d.ts +51 -0
  74. package/dist/utils/change-utils.js +100 -0
  75. package/dist/utils/file-system.d.ts +11 -0
  76. package/dist/utils/file-system.js +50 -2
  77. package/dist/utils/index.d.ts +3 -1
  78. package/dist/utils/index.js +4 -1
  79. package/package.json +5 -1
  80. package/schemas/spec-driven/schema.yaml +148 -0
  81. package/schemas/spec-driven/templates/design.md +19 -0
  82. package/schemas/spec-driven/templates/proposal.md +23 -0
  83. package/schemas/spec-driven/templates/spec.md +8 -0
  84. package/schemas/spec-driven/templates/tasks.md +9 -0
  85. package/schemas/tdd/schema.yaml +213 -0
  86. package/schemas/tdd/templates/docs.md +15 -0
  87. package/schemas/tdd/templates/implementation.md +11 -0
  88. package/schemas/tdd/templates/spec.md +11 -0
  89. package/schemas/tdd/templates/test.md +11 -0
@@ -0,0 +1,54 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import fg from 'fast-glob';
4
+ import { FileSystemUtils } from '../../utils/file-system.js';
5
+ /**
6
+ * Detects which artifacts are completed by checking file existence in the change directory.
7
+ * Returns a Set of completed artifact IDs.
8
+ *
9
+ * @param graph - The artifact graph to check
10
+ * @param changeDir - The change directory to scan for files
11
+ * @returns Set of artifact IDs whose generated files exist
12
+ */
13
+ export function detectCompleted(graph, changeDir) {
14
+ const completed = new Set();
15
+ // Handle missing change directory gracefully
16
+ if (!fs.existsSync(changeDir)) {
17
+ return completed;
18
+ }
19
+ for (const artifact of graph.getAllArtifacts()) {
20
+ if (isArtifactComplete(artifact.generates, changeDir)) {
21
+ completed.add(artifact.id);
22
+ }
23
+ }
24
+ return completed;
25
+ }
26
+ /**
27
+ * Checks if an artifact is complete by checking if its generated file(s) exist.
28
+ * Supports both simple paths and glob patterns.
29
+ */
30
+ function isArtifactComplete(generates, changeDir) {
31
+ const fullPattern = path.join(changeDir, generates);
32
+ // Check if it's a glob pattern
33
+ if (isGlobPattern(generates)) {
34
+ return hasGlobMatches(fullPattern);
35
+ }
36
+ // Simple file path - check if file exists
37
+ return fs.existsSync(fullPattern);
38
+ }
39
+ /**
40
+ * Checks if a path contains glob pattern characters.
41
+ */
42
+ function isGlobPattern(pattern) {
43
+ return pattern.includes('*') || pattern.includes('?') || pattern.includes('[');
44
+ }
45
+ /**
46
+ * Checks if a glob pattern has any matches.
47
+ * Normalizes Windows backslashes to forward slashes for cross-platform glob compatibility.
48
+ */
49
+ function hasGlobMatches(pattern) {
50
+ const normalizedPattern = FileSystemUtils.toPosixPath(pattern);
51
+ const matches = fg.sync(normalizedPattern, { onlyFiles: true });
52
+ return matches.length > 0;
53
+ }
54
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod';
2
+ export declare const ArtifactSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ generates: z.ZodString;
5
+ description: z.ZodString;
6
+ template: z.ZodString;
7
+ instruction: z.ZodOptional<z.ZodString>;
8
+ requires: z.ZodDefault<z.ZodArray<z.ZodString>>;
9
+ }, z.core.$strip>;
10
+ export declare const ApplyPhaseSchema: z.ZodObject<{
11
+ requires: z.ZodArray<z.ZodString>;
12
+ tracks: z.ZodOptional<z.ZodNullable<z.ZodString>>;
13
+ instruction: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>;
15
+ export declare const SchemaYamlSchema: z.ZodObject<{
16
+ name: z.ZodString;
17
+ version: z.ZodNumber;
18
+ description: z.ZodOptional<z.ZodString>;
19
+ artifacts: z.ZodArray<z.ZodObject<{
20
+ id: z.ZodString;
21
+ generates: z.ZodString;
22
+ description: z.ZodString;
23
+ template: z.ZodString;
24
+ instruction: z.ZodOptional<z.ZodString>;
25
+ requires: z.ZodDefault<z.ZodArray<z.ZodString>>;
26
+ }, z.core.$strip>>;
27
+ apply: z.ZodOptional<z.ZodObject<{
28
+ requires: z.ZodArray<z.ZodString>;
29
+ tracks: z.ZodOptional<z.ZodNullable<z.ZodString>>;
30
+ instruction: z.ZodOptional<z.ZodString>;
31
+ }, z.core.$strip>>;
32
+ }, z.core.$strip>;
33
+ export type Artifact = z.infer<typeof ArtifactSchema>;
34
+ export type ApplyPhase = z.infer<typeof ApplyPhaseSchema>;
35
+ export type SchemaYaml = z.infer<typeof SchemaYamlSchema>;
36
+ export declare const ChangeMetadataSchema: z.ZodObject<{
37
+ schema: z.ZodString;
38
+ created: z.ZodOptional<z.ZodString>;
39
+ }, z.core.$strip>;
40
+ export type ChangeMetadata = z.infer<typeof ChangeMetadataSchema>;
41
+ export type CompletedSet = Set<string>;
42
+ export interface BlockedArtifacts {
43
+ [artifactId: string]: string[];
44
+ }
45
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ // Artifact definition schema
3
+ export const ArtifactSchema = z.object({
4
+ id: z.string().min(1, { error: 'Artifact ID is required' }),
5
+ generates: z.string().min(1, { error: 'generates field is required' }),
6
+ description: z.string(),
7
+ template: z.string().min(1, { error: 'template field is required' }),
8
+ instruction: z.string().optional(),
9
+ requires: z.array(z.string()).default([]),
10
+ });
11
+ // Apply phase configuration for schema-aware apply instructions
12
+ export const ApplyPhaseSchema = z.object({
13
+ // Artifact IDs that must exist before apply is available
14
+ requires: z.array(z.string()).min(1, { error: 'At least one required artifact' }),
15
+ // Path to file with checkboxes for progress (relative to change dir), or null if no tracking
16
+ tracks: z.string().nullable().optional(),
17
+ // Custom guidance for the apply phase
18
+ instruction: z.string().optional(),
19
+ });
20
+ // Full schema YAML structure
21
+ export const SchemaYamlSchema = z.object({
22
+ name: z.string().min(1, { error: 'Schema name is required' }),
23
+ version: z.number().int().positive({ error: 'Version must be a positive integer' }),
24
+ description: z.string().optional(),
25
+ artifacts: z.array(ArtifactSchema).min(1, { error: 'At least one artifact required' }),
26
+ // Optional apply phase configuration (for schema-aware apply instructions)
27
+ apply: ApplyPhaseSchema.optional(),
28
+ });
29
+ // Per-change metadata schema
30
+ // Note: schema field is validated at parse time against available schemas
31
+ // using a lazy import to avoid circular dependencies
32
+ export const ChangeMetadataSchema = z.object({
33
+ // Required: which workflow schema this change uses
34
+ schema: z.string().min(1, { message: 'schema is required' }),
35
+ // Optional: creation timestamp (ISO date string)
36
+ created: z
37
+ .string()
38
+ .regex(/^\d{4}-\d{2}-\d{2}$/, {
39
+ message: 'created must be YYYY-MM-DD format',
40
+ })
41
+ .optional(),
42
+ });
43
+ //# sourceMappingURL=types.js.map
@@ -281,7 +281,13 @@ export const COMMAND_REGISTRY = [
281
281
  description: 'Uninstall completion script for a shell',
282
282
  acceptsPositional: true,
283
283
  positionalType: 'shell',
284
- flags: [],
284
+ flags: [
285
+ {
286
+ name: 'yes',
287
+ short: 'y',
288
+ description: 'Skip confirmation prompts',
289
+ },
290
+ ],
285
291
  },
286
292
  ],
287
293
  },
@@ -1,6 +1,20 @@
1
1
  import { CompletionGenerator } from './types.js';
2
- import { InstallationResult } from './installers/zsh-installer.js';
3
2
  import { SupportedShell } from '../../utils/shell-detection.js';
3
+ /**
4
+ * Common installation result interface
5
+ */
6
+ export interface InstallationResult {
7
+ success: boolean;
8
+ installedPath?: string;
9
+ backupPath?: string;
10
+ message: string;
11
+ instructions?: string[];
12
+ warnings?: string[];
13
+ isOhMyZsh?: boolean;
14
+ zshrcConfigured?: boolean;
15
+ bashrcConfigured?: boolean;
16
+ profileConfigured?: boolean;
17
+ }
4
18
  /**
5
19
  * Interface for completion installers
6
20
  */
@@ -11,7 +25,6 @@ export interface CompletionInstaller {
11
25
  message: string;
12
26
  }>;
13
27
  }
14
- export type { InstallationResult };
15
28
  /**
16
29
  * Factory for creating completion generators and installers
17
30
  * This design makes it easy to add support for additional shells
@@ -1,11 +1,17 @@
1
1
  import { ZshGenerator } from './generators/zsh-generator.js';
2
+ import { BashGenerator } from './generators/bash-generator.js';
3
+ import { FishGenerator } from './generators/fish-generator.js';
4
+ import { PowerShellGenerator } from './generators/powershell-generator.js';
2
5
  import { ZshInstaller } from './installers/zsh-installer.js';
6
+ import { BashInstaller } from './installers/bash-installer.js';
7
+ import { FishInstaller } from './installers/fish-installer.js';
8
+ import { PowerShellInstaller } from './installers/powershell-installer.js';
3
9
  /**
4
10
  * Factory for creating completion generators and installers
5
11
  * This design makes it easy to add support for additional shells
6
12
  */
7
13
  export class CompletionFactory {
8
- static SUPPORTED_SHELLS = ['zsh'];
14
+ static SUPPORTED_SHELLS = ['zsh', 'bash', 'fish', 'powershell'];
9
15
  /**
10
16
  * Create a completion generator for the specified shell
11
17
  *
@@ -17,6 +23,12 @@ export class CompletionFactory {
17
23
  switch (shell) {
18
24
  case 'zsh':
19
25
  return new ZshGenerator();
26
+ case 'bash':
27
+ return new BashGenerator();
28
+ case 'fish':
29
+ return new FishGenerator();
30
+ case 'powershell':
31
+ return new PowerShellGenerator();
20
32
  default:
21
33
  throw new Error(`Unsupported shell: ${shell}`);
22
34
  }
@@ -32,6 +44,12 @@ export class CompletionFactory {
32
44
  switch (shell) {
33
45
  case 'zsh':
34
46
  return new ZshInstaller();
47
+ case 'bash':
48
+ return new BashInstaller();
49
+ case 'fish':
50
+ return new FishInstaller();
51
+ case 'powershell':
52
+ return new PowerShellInstaller();
35
53
  default:
36
54
  throw new Error(`Unsupported shell: ${shell}`);
37
55
  }
@@ -0,0 +1,32 @@
1
+ import { CompletionGenerator, CommandDefinition } from '../types.js';
2
+ /**
3
+ * Generates Bash completion scripts for the OpenSpec CLI.
4
+ * Follows Bash completion conventions using complete builtin and COMPREPLY array.
5
+ */
6
+ export declare class BashGenerator implements CompletionGenerator {
7
+ readonly shell: "bash";
8
+ /**
9
+ * Generate a Bash completion script
10
+ *
11
+ * @param commands - Command definitions to generate completions for
12
+ * @returns Bash completion script as a string
13
+ */
14
+ generate(commands: CommandDefinition[]): string;
15
+ /**
16
+ * Generate completion case logic for a command
17
+ */
18
+ private generateCommandCase;
19
+ /**
20
+ * Generate argument completion (flags and positional arguments)
21
+ */
22
+ private generateArgumentCompletion;
23
+ /**
24
+ * Generate positional argument completion based on type
25
+ */
26
+ private generatePositionalCompletion;
27
+ /**
28
+ * Escape command/subcommand names for safe use in Bash scripts
29
+ */
30
+ private escapeCommandName;
31
+ }
32
+ //# sourceMappingURL=bash-generator.d.ts.map
@@ -0,0 +1,174 @@
1
+ import { BASH_DYNAMIC_HELPERS } from '../templates/bash-templates.js';
2
+ /**
3
+ * Generates Bash completion scripts for the OpenSpec CLI.
4
+ * Follows Bash completion conventions using complete builtin and COMPREPLY array.
5
+ */
6
+ export class BashGenerator {
7
+ shell = 'bash';
8
+ /**
9
+ * Generate a Bash completion script
10
+ *
11
+ * @param commands - Command definitions to generate completions for
12
+ * @returns Bash completion script as a string
13
+ */
14
+ generate(commands) {
15
+ // Build command list for top-level completions
16
+ const commandList = commands.map(c => this.escapeCommandName(c.name)).join(' ');
17
+ // Build command cases using push() for loop clarity
18
+ const caseLines = [];
19
+ for (const cmd of commands) {
20
+ caseLines.push(` ${cmd.name})`);
21
+ caseLines.push(...this.generateCommandCase(cmd, ' '));
22
+ caseLines.push(' ;;');
23
+ }
24
+ const commandCases = caseLines.join('\n');
25
+ // Dynamic completion helpers from template
26
+ const helpers = BASH_DYNAMIC_HELPERS;
27
+ // Assemble final script with template literal
28
+ return `# Bash completion script for OpenSpec CLI
29
+ # Auto-generated - do not edit manually
30
+
31
+ _openspec_completion() {
32
+ local cur prev words cword
33
+
34
+ # Use _init_completion if available (from bash-completion package)
35
+ # The -n : option prevents colons from being treated as word separators
36
+ # (important for spec/change IDs that may contain colons)
37
+ # Otherwise, fall back to manual initialization
38
+ if declare -F _init_completion >/dev/null 2>&1; then
39
+ _init_completion -n : || return
40
+ else
41
+ # Manual fallback when bash-completion is not installed
42
+ COMPREPLY=()
43
+ cur="\${COMP_WORDS[COMP_CWORD]}"
44
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
45
+ words=("\${COMP_WORDS[@]}")
46
+ cword=$COMP_CWORD
47
+ fi
48
+
49
+ local cmd="\${words[1]}"
50
+ local subcmd="\${words[2]}"
51
+
52
+ # Top-level commands
53
+ if [[ $cword -eq 1 ]]; then
54
+ local commands="${commandList}"
55
+ COMPREPLY=($(compgen -W "$commands" -- "$cur"))
56
+ return 0
57
+ fi
58
+
59
+ # Command-specific completion
60
+ case "$cmd" in
61
+ ${commandCases}
62
+ esac
63
+
64
+ return 0
65
+ }
66
+
67
+ ${helpers}
68
+ complete -F _openspec_completion openspec
69
+ `;
70
+ }
71
+ /**
72
+ * Generate completion case logic for a command
73
+ */
74
+ generateCommandCase(cmd, indent) {
75
+ const lines = [];
76
+ // Handle subcommands
77
+ if (cmd.subcommands && cmd.subcommands.length > 0) {
78
+ // First, check if user is typing a flag for the parent command
79
+ if (cmd.flags.length > 0) {
80
+ lines.push(`${indent}if [[ "$cur" == -* ]]; then`);
81
+ const flags = cmd.flags.map(f => {
82
+ const parts = [];
83
+ if (f.short)
84
+ parts.push(`-${f.short}`);
85
+ parts.push(`--${f.name}`);
86
+ return parts.join(' ');
87
+ }).join(' ');
88
+ lines.push(`${indent} local flags="${flags}"`);
89
+ lines.push(`${indent} COMPREPLY=($(compgen -W "$flags" -- "$cur"))`);
90
+ lines.push(`${indent} return 0`);
91
+ lines.push(`${indent}fi`);
92
+ lines.push('');
93
+ }
94
+ lines.push(`${indent}if [[ $cword -eq 2 ]]; then`);
95
+ lines.push(`${indent} local subcommands="` + cmd.subcommands.map(s => this.escapeCommandName(s.name)).join(' ') + '"');
96
+ lines.push(`${indent} COMPREPLY=($(compgen -W "$subcommands" -- "$cur"))`);
97
+ lines.push(`${indent} return 0`);
98
+ lines.push(`${indent}fi`);
99
+ lines.push('');
100
+ lines.push(`${indent}case "$subcmd" in`);
101
+ for (const subcmd of cmd.subcommands) {
102
+ lines.push(`${indent} ${subcmd.name})`);
103
+ lines.push(...this.generateArgumentCompletion(subcmd, indent + ' '));
104
+ lines.push(`${indent} ;;`);
105
+ }
106
+ lines.push(`${indent}esac`);
107
+ }
108
+ else {
109
+ // No subcommands, just complete arguments
110
+ lines.push(...this.generateArgumentCompletion(cmd, indent));
111
+ }
112
+ return lines;
113
+ }
114
+ /**
115
+ * Generate argument completion (flags and positional arguments)
116
+ */
117
+ generateArgumentCompletion(cmd, indent) {
118
+ const lines = [];
119
+ // Check for flag completion
120
+ if (cmd.flags.length > 0) {
121
+ lines.push(`${indent}if [[ "$cur" == -* ]]; then`);
122
+ const flags = cmd.flags.map(f => {
123
+ const parts = [];
124
+ if (f.short)
125
+ parts.push(`-${f.short}`);
126
+ parts.push(`--${f.name}`);
127
+ return parts.join(' ');
128
+ }).join(' ');
129
+ lines.push(`${indent} local flags="${flags}"`);
130
+ lines.push(`${indent} COMPREPLY=($(compgen -W "$flags" -- "$cur"))`);
131
+ lines.push(`${indent} return 0`);
132
+ lines.push(`${indent}fi`);
133
+ lines.push('');
134
+ }
135
+ // Handle positional completions
136
+ if (cmd.acceptsPositional) {
137
+ lines.push(...this.generatePositionalCompletion(cmd.positionalType, indent));
138
+ }
139
+ return lines;
140
+ }
141
+ /**
142
+ * Generate positional argument completion based on type
143
+ */
144
+ generatePositionalCompletion(positionalType, indent) {
145
+ const lines = [];
146
+ switch (positionalType) {
147
+ case 'change-id':
148
+ lines.push(`${indent}_openspec_complete_changes`);
149
+ break;
150
+ case 'spec-id':
151
+ lines.push(`${indent}_openspec_complete_specs`);
152
+ break;
153
+ case 'change-or-spec-id':
154
+ lines.push(`${indent}_openspec_complete_items`);
155
+ break;
156
+ case 'shell':
157
+ lines.push(`${indent}local shells="zsh bash fish powershell"`);
158
+ lines.push(`${indent}COMPREPLY=($(compgen -W "$shells" -- "$cur"))`);
159
+ break;
160
+ case 'path':
161
+ lines.push(`${indent}COMPREPLY=($(compgen -f -- "$cur"))`);
162
+ break;
163
+ }
164
+ return lines;
165
+ }
166
+ /**
167
+ * Escape command/subcommand names for safe use in Bash scripts
168
+ */
169
+ escapeCommandName(name) {
170
+ // Escape shell metacharacters to prevent command injection
171
+ return name.replace(/["\$`\\]/g, '\\$&');
172
+ }
173
+ }
174
+ //# sourceMappingURL=bash-generator.js.map
@@ -0,0 +1,32 @@
1
+ import { CompletionGenerator, CommandDefinition } from '../types.js';
2
+ /**
3
+ * Generates Fish completion scripts for the OpenSpec CLI.
4
+ * Follows Fish completion conventions using the complete command.
5
+ */
6
+ export declare class FishGenerator implements CompletionGenerator {
7
+ readonly 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: CommandDefinition[]): string;
15
+ /**
16
+ * Generate completions for a specific command
17
+ */
18
+ private generateCommandCompletions;
19
+ /**
20
+ * Generate flag completion
21
+ */
22
+ private generateFlagCompletion;
23
+ /**
24
+ * Generate positional argument completion
25
+ */
26
+ private generatePositionalCompletion;
27
+ /**
28
+ * Escape description text for Fish
29
+ */
30
+ private escapeDescription;
31
+ }
32
+ //# sourceMappingURL=fish-generator.d.ts.map
@@ -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