feat-forge 1.0.1

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 (93) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +350 -0
  3. package/dist/cli.js +306 -0
  4. package/dist/commands/AbstractCommands.js +16 -0
  5. package/dist/commands/AgentCommands.js +14 -0
  6. package/dist/commands/BranchCommands.js +400 -0
  7. package/dist/commands/CompletionCommands.js +702 -0
  8. package/dist/commands/EnvCommands.js +56 -0
  9. package/dist/commands/FeatureCommands.js +4 -0
  10. package/dist/commands/FixCommands.js +4 -0
  11. package/dist/commands/InitCommands.js +380 -0
  12. package/dist/commands/MaintenanceCommands.js +39 -0
  13. package/dist/commands/ModeCommands.js +15 -0
  14. package/dist/commands/ProxyCommands.js +14 -0
  15. package/dist/commands/ReleaseCommands.js +4 -0
  16. package/dist/commands/ServicesCommands.js +95 -0
  17. package/dist/commands/SubBranchCommands.js +49 -0
  18. package/dist/commands/types/InitOptions.js +1 -0
  19. package/dist/foundation/BranchContext.js +427 -0
  20. package/dist/foundation/ForgeConfig.js +264 -0
  21. package/dist/foundation/ForgeConfigFile.js +391 -0
  22. package/dist/foundation/ForgeContext.js +169 -0
  23. package/dist/foundation/NpmHelper.js +131 -0
  24. package/dist/foundation/PathHelper.js +56 -0
  25. package/dist/foundation/PortAllocator.js +192 -0
  26. package/dist/foundation/Proxy.js +176 -0
  27. package/dist/foundation/Repository.js +431 -0
  28. package/dist/foundation/errors/ForgeError.js +9 -0
  29. package/dist/foundation/errors/_error.config.js +12 -0
  30. package/dist/foundation/errors/generated/ForgeBadStateError.js +11 -0
  31. package/dist/foundation/errors/generated/ForgeConfigError.js +11 -0
  32. package/dist/foundation/errors/generated/ForgeExpectMainRepositoryError.js +11 -0
  33. package/dist/foundation/errors/generated/ForgeModeNotDefinedError.js +11 -0
  34. package/dist/foundation/errors/generated/ForgeNotInActiveBranchError.js +11 -0
  35. package/dist/foundation/errors/generated/ForgePortAllocationsLoadError.js +11 -0
  36. package/dist/foundation/errors/generated/ForgePortNotAssignedError.js +11 -0
  37. package/dist/foundation/errors/generated/ForgePortRangeExhaustedError.js +11 -0
  38. package/dist/foundation/errors/generated/ForgeServicesScanError.js +11 -0
  39. package/dist/foundation/errors/generated/ForgeServicesValidationError.js +11 -0
  40. package/dist/foundation/errors/index.js +13 -0
  41. package/dist/foundation/types/AIAgent.js +1 -0
  42. package/dist/foundation/types/AIAgentName.js +11 -0
  43. package/dist/foundation/types/DeepPartial.js +1 -0
  44. package/dist/foundation/types/IDE.js +1 -0
  45. package/dist/foundation/types/IDEName.js +7 -0
  46. package/dist/foundation/types/ModeConfig.js +1 -0
  47. package/dist/foundation/types/RepositoryInfos.js +1 -0
  48. package/dist/foundation/types/Services.js +156 -0
  49. package/dist/foundation/types/ShellName.js +11 -0
  50. package/dist/lib/agents.js +47 -0
  51. package/dist/lib/bootstrap.js +54 -0
  52. package/dist/lib/branch.js +4 -0
  53. package/dist/lib/config.js +65 -0
  54. package/dist/lib/constants.js +13 -0
  55. package/dist/lib/env.js +20 -0
  56. package/dist/lib/fs.js +156 -0
  57. package/dist/lib/git.js +170 -0
  58. package/dist/lib/hooks.js +98 -0
  59. package/dist/lib/ide.js +75 -0
  60. package/dist/lib/merger.js +103 -0
  61. package/dist/lib/platform.js +13 -0
  62. package/dist/lib/prompt.js +134 -0
  63. package/dist/lib/proxy-dashboard.js +75 -0
  64. package/dist/lib/scanner.js +118 -0
  65. package/dist/lib/services.js +132 -0
  66. package/dist/lib/slug.js +35 -0
  67. package/dist/lib/templates.js +115 -0
  68. package/dist/lib/validator.js +15 -0
  69. package/dist/templates/SPEC.md +21 -0
  70. package/dist/templates/TODO.md +5 -0
  71. package/dist/templates/agent/001.general.Omnibus.agent.md +4 -0
  72. package/dist/templates/agent/002.discovery.Inventorius.agent.md +4 -0
  73. package/dist/templates/agent/003.design.Architecturius.agent.md +8 -0
  74. package/dist/templates/agent/004.plan.Strategos.agent.md +8 -0
  75. package/dist/templates/agent/005.tdd.TestDrivenCodificius.agent.md +8 -0
  76. package/dist/templates/agent/006.code.Codificius.agent.md +8 -0
  77. package/dist/templates/agent/007.simplify.Consolidarius.agent.md +8 -0
  78. package/dist/templates/agent/008.review.Auditorix.agent.md +8 -0
  79. package/dist/templates/agent/009.testwriter.TestScriptor.agent.md +8 -0
  80. package/dist/templates/agent/010.testexecutor.TestExecutor.agent.md +8 -0
  81. package/dist/templates/agent/011.commit.Scribus.agent.md +10 -0
  82. package/dist/templates/agent/CONTEXT.code.md +145 -0
  83. package/dist/templates/agent/CONTEXT.spec.md +98 -0
  84. package/dist/templates/agent/Copilot/Code.agent.md +28 -0
  85. package/dist/templates/agent/Copilot/CodeCommit.agent.md +16 -0
  86. package/dist/templates/agent/Copilot/Feature-Builder.agent.md +49 -0
  87. package/dist/templates/agent/Copilot/Reviewer.agent.md +17 -0
  88. package/dist/templates/agent/Copilot/Simplifier.agent.md +21 -0
  89. package/dist/templates/agent/Copilot/Specs.agent.md +66 -0
  90. package/dist/templates/agent/Copilot/SpecsCommit.agent.md +19 -0
  91. package/dist/templates/agent/Copilot/TODO-Reader.agent.md +18 -0
  92. package/dist/templates/agent/Copilot/Tester.agent.md +12 -0
  93. package/package.json +76 -0
@@ -0,0 +1,702 @@
1
+ import { readdir } from 'fs/promises';
2
+ import { pathExists } from '../lib/fs.js';
3
+ import { AbstractCommands } from './AbstractCommands.js';
4
+ /**
5
+ * Commands for managing shell completion/autocomplete
6
+ */
7
+ export class CompletionCommands extends AbstractCommands {
8
+ program;
9
+ constructor(config, program) {
10
+ super(config);
11
+ this.program = program;
12
+ }
13
+ // ============================================================================
14
+ // PUBLIC COMMAND METHODS
15
+ // ============================================================================
16
+ /**
17
+ * Generate and display shell completion script for the specified shell.
18
+ * Outputs only the script to stdout for piping or sourcing.
19
+ *
20
+ * @param shell - The target shell type (bash, zsh, or fish)
21
+ */
22
+ /**
23
+ * Generate and display shell completion script for the specified shell.
24
+ * Outputs only the script to stdout for piping or sourcing.
25
+ *
26
+ * @param shell - The target shell type (bash, zsh, or fish)
27
+ */
28
+ async generate(shell) {
29
+ const script = await this.generateCompletionScript(shell);
30
+ console.log(script);
31
+ }
32
+ // ============================================================================
33
+ // PRIVATE UTILITY METHODS
34
+ // ============================================================================
35
+ /**
36
+ * Generate the appropriate completion script based on shell type.
37
+ *
38
+ * @param shell - The target shell type
39
+ * @returns The generated completion script as a string
40
+ */
41
+ async generateCompletionScript(shell) {
42
+ switch (shell) {
43
+ case 'bash':
44
+ return this.generateBashCompletion();
45
+ case 'zsh':
46
+ return this.generateZshCompletion();
47
+ case 'fish':
48
+ return this.generateFishCompletion();
49
+ case 'powershell':
50
+ case 'pwsh':
51
+ return this.generatePowerShellCompletion();
52
+ default:
53
+ throw new Error(`Unsupported shell: ${shell}`);
54
+ }
55
+ }
56
+ /**
57
+ * Get list of available feature slugs for contextual completion.
58
+ * Returns empty array if features directory doesn't exist or if there's an error.
59
+ *
60
+ * @returns Array of feature slugs
61
+ */
62
+ async getAvailableFeatures() {
63
+ try {
64
+ if (!(await pathExists(this.context.paths.worktreesRoot))) {
65
+ return [];
66
+ }
67
+ const entries = await readdir(this.context.paths.worktreesRoot, { withFileTypes: true });
68
+ return entries
69
+ .filter((entry) => entry.isDirectory())
70
+ .map((entry) => entry.name)
71
+ .sort();
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ }
77
+ /**
78
+ * Extract command information from Commander.js program.
79
+ * Recursively extracts all commands and their subcommands.
80
+ *
81
+ * @param command - Commander.js Command object
82
+ * @returns Structured command information
83
+ */
84
+ extractCommandInfo(command) {
85
+ const name = command.name();
86
+ const description = command.description();
87
+ const hasSlugArgument = command.registeredArguments?.some((arg) => arg._name === 'slug' && arg.required) ?? false;
88
+ const subcommands = command.commands.filter((cmd) => !cmd.name().includes('help')).map((cmd) => this.extractCommandInfo(cmd));
89
+ return { name, description, subcommands, hasSlugArgument };
90
+ }
91
+ /**
92
+ * Get all main commands from the program.
93
+ *
94
+ * @returns Array of command information
95
+ */
96
+ getMainCommands() {
97
+ return this.program.commands.filter((cmd) => !cmd.name().includes('help')).map((cmd) => this.extractCommandInfo(cmd));
98
+ }
99
+ /**
100
+ * Find a specific command by name.
101
+ *
102
+ * @param commandName - Name of the command to find
103
+ * @returns Command information or undefined
104
+ */
105
+ findCommand(commandName) {
106
+ return this.getMainCommands().find((cmd) => cmd.name === commandName);
107
+ }
108
+ escapeDoubleQuotes(value) {
109
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
110
+ }
111
+ escapeSingleQuotes(value) {
112
+ return value.replace(/'/g, "''");
113
+ }
114
+ /**
115
+ * Get list of command names that have a required slug argument.
116
+ *
117
+ * @returns Array of command names
118
+ */
119
+ getCommandsWithSlug() {
120
+ const commands = [];
121
+ const checkCommand = (cmd, parentName) => {
122
+ const fullName = parentName ? `${parentName}|${cmd.name}` : cmd.name;
123
+ if (cmd.hasSlugArgument) {
124
+ commands.push(cmd.name);
125
+ }
126
+ cmd.subcommands.forEach((sub) => checkCommand(sub, cmd.name));
127
+ };
128
+ this.getMainCommands().forEach((cmd) => checkCommand(cmd));
129
+ return commands;
130
+ }
131
+ /**
132
+ * Generate bash completion script.
133
+ *
134
+ * @returns Bash completion script content
135
+ */
136
+ generateBashCompletion() {
137
+ const mainCommands = this.getMainCommands();
138
+ const featureCmd = this.findCommand('feature');
139
+ const modeCmd = this.findCommand('mode');
140
+ const agentCmd = this.findCommand('agent');
141
+ // Build command lists
142
+ const commands = mainCommands.map((cmd) => cmd.name).join(' ');
143
+ const featureCommands = featureCmd?.subcommands.map((cmd) => cmd.name).join(' ') || '';
144
+ const modeCommands = modeCmd?.subcommands.map((cmd) => cmd.name).join(' ') || '';
145
+ const agentCommands = agentCmd?.subcommands.map((cmd) => cmd.name).join(' ') || '';
146
+ // Limit slug completions to active feature commands only
147
+ const activeFeatureSlugCommands = ['stop', 'archive', 'resync', 'merge', 'rebase', 'open'];
148
+ const featureWithActiveSlug = featureCmd?.subcommands.filter((cmd) => activeFeatureSlugCommands.includes(cmd.name)).map((cmd) => cmd.name) || [];
149
+ const mainWithActiveSlug = mainCommands.filter((cmd) => ['merge', 'rebase', 'open'].includes(cmd.name)).map((cmd) => cmd.name);
150
+ const featureSlugCase = featureWithActiveSlug.length > 0
151
+ ? ` ${featureWithActiveSlug.join('|')})
152
+ # Suggest available features
153
+ if [[ \${cword} -eq 3 ]]; then
154
+ local worktrees_root="\$(_forge_worktrees_root)"
155
+ local features=\$(find "\${worktrees_root}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \\; 2>/dev/null)
156
+ COMPREPLY=( \$(compgen -W "\${features}" -- "\${cur}") )
157
+ return 0
158
+ fi
159
+ ;;
160
+ `
161
+ : '';
162
+ const mainSlugCase = mainWithActiveSlug.length > 0
163
+ ? ` ${mainWithActiveSlug.join('|')})
164
+ # Suggest available features for commands with slug argument
165
+ if [[ \${cword} -eq 2 ]]; then
166
+ local worktrees_root="\$(_forge_worktrees_root)"
167
+ local features=\$(find "\${worktrees_root}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \\; 2>/dev/null)
168
+ COMPREPLY=( \$(compgen -W "\${features}" -- "\${cur}") )
169
+ return 0
170
+ fi
171
+ ;;
172
+ `
173
+ : '';
174
+ return `# forge bash completion script
175
+
176
+ _forge_find_config() {
177
+ local dir="\$1"
178
+ while [[ -n "\${dir}" && "\${dir}" != "/" ]]; do
179
+ if [[ -f "\${dir}/.feat-forge.json" ]]; then
180
+ echo "\${dir}"
181
+ return 0
182
+ fi
183
+ dir="\${dir%/*}"
184
+ done
185
+ return 1
186
+ }
187
+
188
+ _forge_worktrees_root() {
189
+ if [[ -n "\${FORGE_WORKTREES_ROOT}" ]]; then
190
+ echo "\${FORGE_WORKTREES_ROOT}"
191
+ return 0
192
+ fi
193
+ local start="\${PWD}"
194
+ local config_root
195
+ config_root="\$(_forge_find_config "\${start}")" || true
196
+ if [[ -n "\${config_root}" ]]; then
197
+ local worktrees
198
+ worktrees=\$(node -e 'const fs=require("fs");const path=require("path");
199
+ try{
200
+ const file=process.argv[1];
201
+ const configRoot=path.dirname(file);
202
+ const data=JSON.parse(fs.readFileSync(file,"utf8"));
203
+ let rootDir=data.rootDir;
204
+ if(rootDir){ if(!path.isAbsolute(rootDir)) rootDir=path.join(configRoot, rootDir); }
205
+ else { rootDir=configRoot; }
206
+ const folders=(data.options && data.options.folders) || data.folders || {};
207
+ const worktrees=folders.worktrees || "features";
208
+ process.stdout.write(path.join(rootDir, worktrees));
209
+ }catch(e){}
210
+ ' "\${config_root}/.feat-forge.json" 2>/dev/null)
211
+ if [[ -n "\${worktrees}" ]]; then
212
+ echo "\${worktrees}"
213
+ return 0
214
+ fi
215
+ echo "\${config_root}/features"
216
+ return 0
217
+ fi
218
+ echo "features"
219
+ }
220
+
221
+ _forge_completion() {
222
+ local cur prev words cword
223
+ _init_completion || return
224
+
225
+ # Main commands available at root level
226
+ local commands="${commands}"
227
+
228
+ # Subcommands for each main command
229
+ local feature_commands="${featureCommands}"
230
+ local mode_commands="${modeCommands}"
231
+ local agent_commands="${agentCommands}"
232
+
233
+ # Get previous word for context
234
+ case "\${words[1]}" in
235
+ feature)
236
+ case "\${words[2]}" in
237
+ ${featureSlugCase} *)
238
+ # Suggest feature subcommands
239
+ if [[ \${cword} -eq 2 ]]; then
240
+ COMPREPLY=( \$(compgen -W "\${feature_commands}" -- "\${cur}") )
241
+ return 0
242
+ fi
243
+ ;;
244
+ esac
245
+ ;;
246
+ mode)
247
+ # Suggest mode subcommands
248
+ if [[ \${cword} -eq 2 ]]; then
249
+ COMPREPLY=( \$(compgen -W "\${mode_commands}" -- "\${cur}") )
250
+ return 0
251
+ fi
252
+ ;;
253
+ agent)
254
+ # Suggest agent subcommands
255
+ if [[ \${cword} -eq 2 ]]; then
256
+ COMPREPLY=( \$(compgen -W "\${agent_commands}" -- "\${cur}") )
257
+ return 0
258
+ fi
259
+ ;;
260
+ ${mainSlugCase} completion)
261
+ # Suggest shell types for completion
262
+ if [[ \${cword} -eq 2 ]]; then
263
+ COMPREPLY=( \$(compgen -W "bash zsh fish powershell pwsh" -- "\${cur}") )
264
+ return 0
265
+ fi
266
+ ;;
267
+ *)
268
+ # Suggest main commands at root level
269
+ if [[ \${cword} -eq 1 ]]; then
270
+ COMPREPLY=( \$(compgen -W "\${commands}" -- "\${cur}") )
271
+ return 0
272
+ fi
273
+ ;;
274
+ esac
275
+ }
276
+
277
+ # Register completion for forge command
278
+ complete -F _forge_completion forge
279
+
280
+ # Installation instructions:
281
+ # Option 1 - Add to ~/.bashrc:
282
+ # source <(forge completion bash)
283
+ #
284
+ # Option 2 - Save to file:
285
+ # forge completion bash > ~/.local/share/bash-completion/completions/forge
286
+ # # Or system-wide: /etc/bash_completion.d/forge
287
+ `;
288
+ }
289
+ /**
290
+ * Generate zsh completion script.
291
+ *
292
+ * @returns Zsh completion script content
293
+ */
294
+ generateZshCompletion() {
295
+ const mainCommands = this.getMainCommands();
296
+ const featureCmd = this.findCommand('feature');
297
+ const modeCmd = this.findCommand('mode');
298
+ const agentCmd = this.findCommand('agent');
299
+ // Build command arrays with descriptions
300
+ const commandsArray = mainCommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n');
301
+ const featureArray = featureCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') || '';
302
+ const modeArray = modeCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') || '';
303
+ const agentArray = agentCmd?.subcommands.map((cmd) => ` '${cmd.name}:${cmd.description.replace(/'/g, "''")}'`).join('\n') || '';
304
+ // Limit slug completions to active feature commands only
305
+ const activeFeatureSlugCommands = ['stop', 'archive', 'resync', 'merge', 'rebase', 'open'];
306
+ const featureWithActiveSlug = featureCmd?.subcommands.filter((cmd) => activeFeatureSlugCommands.includes(cmd.name)).map((cmd) => cmd.name) || [];
307
+ const mainWithActiveSlug = mainCommands.filter((cmd) => ['merge', 'rebase', 'open'].includes(cmd.name)).map((cmd) => cmd.name);
308
+ const featureSlugCase = featureWithActiveSlug.length > 0
309
+ ? ` ${featureWithActiveSlug.join('|')})
310
+ # Suggest available features
311
+ local features
312
+ local worktrees_root="\$(_forge_worktrees_root)"
313
+ features=(\${(f)"\$(find "\${worktrees_root}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \\; 2>/dev/null)"})
314
+ _describe 'feature slug' features
315
+ ;;
316
+ `
317
+ : '';
318
+ const mainSlugCase = mainWithActiveSlug.length > 0
319
+ ? ` ${mainWithActiveSlug.join('|')})
320
+ # Suggest available features for shortcut commands
321
+ local features
322
+ local worktrees_root="\$(_forge_worktrees_root)"
323
+ features=(\${(f)"\$(find "\${worktrees_root}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \\; 2>/dev/null)"})
324
+ _describe 'feature slug' features
325
+ ;;
326
+ `
327
+ : '';
328
+ return `#compdef forge
329
+ # forge zsh completion script
330
+
331
+ _forge_find_config() {
332
+ local dir="\$1"
333
+ while [[ -n "\${dir}" && "\${dir}" != "/" ]]; do
334
+ if [[ -f "\${dir}/.feat-forge.json" ]]; then
335
+ echo "\${dir}"
336
+ return 0
337
+ fi
338
+ dir="\${dir%/*}"
339
+ done
340
+ return 1
341
+ }
342
+
343
+ _forge_worktrees_root() {
344
+ if [[ -n "\${FORGE_WORKTREES_ROOT}" ]]; then
345
+ echo "\${FORGE_WORKTREES_ROOT}"
346
+ return 0
347
+ fi
348
+ local start="\${PWD}"
349
+ local config_root
350
+ config_root="\$(_forge_find_config "\${start}")" || true
351
+ if [[ -n "\${config_root}" ]]; then
352
+ local worktrees
353
+ worktrees=\$(node -e 'const fs=require("fs");const path=require("path");
354
+ try{
355
+ const file=process.argv[1];
356
+ const configRoot=path.dirname(file);
357
+ const data=JSON.parse(fs.readFileSync(file,"utf8"));
358
+ let rootDir=data.rootDir;
359
+ if(rootDir){ if(!path.isAbsolute(rootDir)) rootDir=path.join(configRoot, rootDir); }
360
+ else { rootDir=configRoot; }
361
+ const folders=(data.options && data.options.folders) || data.folders || {};
362
+ const worktrees=folders.worktrees || "features";
363
+ process.stdout.write(path.join(rootDir, worktrees));
364
+ }catch(e){}
365
+ ' "\${config_root}/.feat-forge.json" 2>/dev/null)
366
+ if [[ -n "\${worktrees}" ]]; then
367
+ echo "\${worktrees}"
368
+ return 0
369
+ fi
370
+ echo "\${config_root}/features"
371
+ return 0
372
+ fi
373
+ echo "features"
374
+ }
375
+
376
+ _forge() {
377
+ local -a commands feature_commands mode_commands agent_commands
378
+
379
+ commands=(
380
+ ${commandsArray}
381
+ )
382
+
383
+ feature_commands=(
384
+ ${featureArray}
385
+ )
386
+
387
+ mode_commands=(
388
+ ${modeArray}
389
+ )
390
+
391
+ agent_commands=(
392
+ ${agentArray}
393
+ )
394
+
395
+ _arguments -C \\
396
+ '1: :->command' \\
397
+ '*::arg:->args'
398
+
399
+ case \${state} in
400
+ command)
401
+ _describe 'forge command' commands
402
+ ;;
403
+ args)
404
+ case \${words[1]} in
405
+ feature)
406
+ case \${words[2]} in
407
+ ${featureSlugCase} *)
408
+ _describe 'feature command' feature_commands
409
+ ;;
410
+ esac
411
+ ;;
412
+ mode)
413
+ _describe 'mode command' mode_commands
414
+ ;;
415
+ agent)
416
+ _describe 'agent command' agent_commands
417
+ ;;
418
+ ${mainSlugCase} completion)
419
+ local shells
420
+ shells=('bash' 'zsh' 'fish' 'powershell' 'pwsh')
421
+ _describe 'shell type' shells
422
+ ;;
423
+ esac
424
+ ;;
425
+ esac
426
+ }
427
+
428
+ # Register the completion function
429
+ compdef _forge forge
430
+
431
+ # Installation instructions:
432
+ # Option 1 - Add to ~/.zshrc:
433
+ # source <(forge completion zsh)
434
+ #
435
+ # Option 2 - Save to fpath directory:
436
+ # forge completion zsh > ~/.zsh/completions/_forge
437
+ # # Add to .zshrc: fpath=(~/.zsh/completions $fpath)
438
+ # # Then run: autoload -Uz compinit && compinit
439
+ `;
440
+ }
441
+ /**
442
+ * Generate fish completion script.
443
+ *
444
+ * @returns Fish completion script content
445
+ */
446
+ generateFishCompletion() {
447
+ const mainCommands = this.getMainCommands();
448
+ const featureCmd = this.findCommand('feature');
449
+ const modeCmd = this.findCommand('mode');
450
+ const agentCmd = this.findCommand('agent');
451
+ // Generate main commands
452
+ const mainCommandsLines = mainCommands
453
+ .map((cmd) => `complete -c forge -n "__fish_use_subcommand" -a ${cmd.name} -d "${cmd.description.replace(/"/g, '\\"')}"`)
454
+ .join('\n');
455
+ // Generate feature subcommands
456
+ const featureSubLines = featureCmd?.subcommands.map((cmd) => cmd.name).join(' ') || '';
457
+ const featureSubCmds = featureCmd?.subcommands
458
+ .map((cmd) => `complete -c forge -n "__fish_seen_subcommand_from feature; and not __fish_seen_subcommand_from ${featureSubLines}" -a ${cmd.name} -d "${cmd.description.replace(/"/g, '\\"')}"`)
459
+ .join('\n') || '';
460
+ // Generate mode subcommands
461
+ const modeSubLines = modeCmd?.subcommands.map((cmd) => cmd.name).join(' ') || '';
462
+ const modeSubCmds = modeCmd?.subcommands
463
+ .map((cmd) => `complete -c forge -n "__fish_seen_subcommand_from mode; and not __fish_seen_subcommand_from ${modeSubLines}" -a ${cmd.name} -d "${cmd.description.replace(/"/g, '\\"')}"`)
464
+ .join('\n') || '';
465
+ // Generate agent subcommands
466
+ const agentSubLines = agentCmd?.subcommands.map((cmd) => cmd.name).join(' ') || '';
467
+ const agentSubCmds = agentCmd?.subcommands
468
+ .map((cmd) => `complete -c forge -n "__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from ${agentSubLines}" -a ${cmd.name} -d "${cmd.description.replace(/"/g, '\\"')}"`)
469
+ .join('\n') || '';
470
+ // Limit slug completions to active feature commands only
471
+ const activeFeatureSlugCommands = ['stop', 'archive', 'resync', 'merge', 'rebase', 'open'];
472
+ const featureWithActiveSlug = featureCmd?.subcommands.filter((cmd) => activeFeatureSlugCommands.includes(cmd.name)) || [];
473
+ const featureSlugCompletions = featureWithActiveSlug
474
+ .map((cmd) => `complete -c forge -n "__fish_seen_subcommand_from feature; and __fish_seen_subcommand_from ${cmd.name}" -a "(__forge_features)"`)
475
+ .join('\n');
476
+ const mainWithActiveSlug = mainCommands.filter((cmd) => ['merge', 'rebase', 'open'].includes(cmd.name));
477
+ const mainSlugCompletions = mainWithActiveSlug
478
+ .map((cmd) => `complete -c forge -n "__fish_seen_subcommand_from ${cmd.name}" -a "(__forge_features)"`)
479
+ .join('\n');
480
+ return `# forge fish completion script
481
+
482
+ # Helper functions to get available features
483
+ function __forge_find_config_root
484
+ set -l dir $PWD
485
+ while test -n "$dir" -a "$dir" != "/"
486
+ if test -f "$dir/.feat-forge.json"
487
+ echo $dir
488
+ return 0
489
+ end
490
+ set dir (dirname $dir)
491
+ end
492
+ return 1
493
+ end
494
+
495
+ function __forge_worktrees_root
496
+ if test -n "$FORGE_WORKTREES_ROOT"
497
+ echo $FORGE_WORKTREES_ROOT
498
+ return 0
499
+ end
500
+ set -l config_root (__forge_find_config_root)
501
+ if test -n "$config_root"
502
+ set -l worktrees (node -e 'const fs=require("fs");const path=require("path");
503
+ try{
504
+ const file=process.argv[1];
505
+ const configRoot=path.dirname(file);
506
+ const data=JSON.parse(fs.readFileSync(file,"utf8"));
507
+ let rootDir=data.rootDir;
508
+ if(rootDir){ if(!path.isAbsolute(rootDir)) rootDir=path.join(configRoot, rootDir); }
509
+ else { rootDir=configRoot; }
510
+ const folders=(data.options && data.options.folders) || data.folders || {};
511
+ const worktrees=folders.worktrees || "features";
512
+ process.stdout.write(path.join(rootDir, worktrees));
513
+ }catch(e){}
514
+ ' "$config_root/.feat-forge.json" 2>/dev/null)
515
+ if test -n "$worktrees"
516
+ echo "$worktrees"
517
+ return 0
518
+ end
519
+ echo "$config_root/features"
520
+ return 0
521
+ end
522
+ echo "features"
523
+ end
524
+
525
+ function __forge_features
526
+ set -l worktrees_root (__forge_worktrees_root)
527
+ if test -d $worktrees_root
528
+ for dir in $worktrees_root/*/
529
+ basename $dir
530
+ end
531
+ end
532
+ end
533
+
534
+ # Disable file completion by default
535
+ complete -c forge -f
536
+
537
+ # Main commands
538
+ ${mainCommandsLines}
539
+
540
+ # Feature subcommands
541
+ ${featureSubCmds}
542
+
543
+ # Feature commands with slug completion
544
+ ${featureSlugCompletions}
545
+
546
+ # Mode subcommands
547
+ ${modeSubCmds}
548
+
549
+ # Agent subcommands
550
+ ${agentSubCmds}
551
+
552
+ # Main commands with feature slug completion
553
+ ${mainSlugCompletions}
554
+
555
+ # Completion command with shell types
556
+ complete -c forge -n "__fish_seen_subcommand_from completion" -a bash -d "Generate bash completion"
557
+ complete -c forge -n "__fish_seen_subcommand_from completion" -a zsh -d "Generate zsh completion"
558
+ complete -c forge -n "__fish_seen_subcommand_from completion" -a fish -d "Generate fish completion"
559
+ complete -c forge -n "__fish_seen_subcommand_from completion" -a powershell -d "Generate PowerShell completion"
560
+ complete -c forge -n "__fish_seen_subcommand_from completion" -a pwsh -d "Generate PowerShell completion"
561
+
562
+ # Installation instructions:
563
+ # Option 1 - Add to ~/.config/fish/config.fish:
564
+ # forge completion fish | source
565
+ #
566
+ # Option 2 - Save to completions directory:
567
+ # forge completion fish > ~/.config/fish/completions/forge.fish
568
+ `;
569
+ }
570
+ /**
571
+ * Generate PowerShell completion script.
572
+ *
573
+ * @returns PowerShell completion script content
574
+ */
575
+ generatePowerShellCompletion() {
576
+ const mainCommands = this.getMainCommands();
577
+ const featureCmd = this.findCommand('feature');
578
+ const modeCmd = this.findCommand('mode');
579
+ const agentCmd = this.findCommand('agent');
580
+ const toPsArray = (items) => items.length > 0 ? items.map((item) => `'${item.replace(/'/g, "''")}'`).join(', ') : '';
581
+ const mainCommandsList = toPsArray(mainCommands.map((cmd) => cmd.name));
582
+ const featureCommandsList = toPsArray(featureCmd?.subcommands.map((cmd) => cmd.name) || []);
583
+ const modeCommandsList = toPsArray(modeCmd?.subcommands.map((cmd) => cmd.name) || []);
584
+ const agentCommandsList = toPsArray(agentCmd?.subcommands.map((cmd) => cmd.name) || []);
585
+ const activeFeatureSlugCommands = ['stop', 'archive', 'resync', 'merge', 'rebase', 'open'];
586
+ const featureWithActiveSlug = featureCmd?.subcommands.filter((cmd) => activeFeatureSlugCommands.includes(cmd.name)) || [];
587
+ const featureSlugCommandsList = toPsArray(featureWithActiveSlug.map((cmd) => cmd.name));
588
+ const mainSlugCommandsList = toPsArray(mainCommands.filter((cmd) => ['merge', 'rebase', 'open'].includes(cmd.name)).map((cmd) => cmd.name));
589
+ const completionShellsList = toPsArray(['bash', 'zsh', 'fish', 'powershell', 'pwsh']);
590
+ return `# forge PowerShell completion script
591
+
592
+ function __ForgeFindConfigRoot {
593
+ $dir = $PWD.Path
594
+ while ($dir -and $dir -ne [System.IO.Path]::GetPathRoot($dir)) {
595
+ if (Test-Path (Join-Path $dir ".feat-forge.json")) {
596
+ return $dir
597
+ }
598
+ $dir = Split-Path -Parent $dir
599
+ }
600
+ return $null
601
+ }
602
+
603
+ function __ForgeWorktreesRoot {
604
+ if ($env:FORGE_WORKTREES_ROOT) { return $env:FORGE_WORKTREES_ROOT }
605
+ $configRoot = __ForgeFindConfigRoot
606
+ if ($configRoot) {
607
+ try {
608
+ $json = Get-Content -Raw -Path (Join-Path $configRoot ".feat-forge.json") | ConvertFrom-Json
609
+ $rootDir = $json.rootDir
610
+ if ($rootDir) {
611
+ if (-not [System.IO.Path]::IsPathRooted($rootDir)) {
612
+ $rootDir = Join-Path $configRoot $rootDir
613
+ }
614
+ } else {
615
+ $rootDir = $configRoot
616
+ }
617
+ if ($json.options -and $json.options.folders -and $json.options.folders.worktrees) {
618
+ return (Join-Path $rootDir $json.options.folders.worktrees)
619
+ }
620
+ if ($json.folders -and $json.folders.worktrees) {
621
+ return (Join-Path $rootDir $json.folders.worktrees)
622
+ }
623
+ } catch {
624
+ }
625
+ return (Join-Path $rootDir "features")
626
+ }
627
+ return "features"
628
+ }
629
+
630
+ function __ForgeFeatureSlugs {
631
+ $worktreesRoot = __ForgeWorktreesRoot
632
+ if (Test-Path $worktreesRoot) {
633
+ Get-ChildItem -Directory -Path $worktreesRoot | ForEach-Object { $_.Name }
634
+ }
635
+ }
636
+
637
+ function __ForgeCompletionResults {
638
+ param([string[]]$Items, [string]$Word)
639
+ if (-not $Items) { return }
640
+ $Items | Where-Object { $_ -like "$Word*" } | ForEach-Object {
641
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
642
+ }
643
+ }
644
+
645
+ $mainCommands = @(${mainCommandsList})
646
+ $featureCommands = @(${featureCommandsList})
647
+ $modeCommands = @(${modeCommandsList})
648
+ $agentCommands = @(${agentCommandsList})
649
+ $featureSlugCommands = @(${featureSlugCommandsList})
650
+ $mainSlugCommands = @(${mainSlugCommandsList})
651
+ $completionShells = @(${completionShellsList})
652
+
653
+ Register-ArgumentCompleter -CommandName forge -ScriptBlock {
654
+ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
655
+
656
+ $elements = $commandAst.CommandElements | ForEach-Object { $_.Value }
657
+
658
+ if ($elements.Count -le 1) {
659
+ __ForgeCompletionResults -Items $mainCommands -Word $wordToComplete
660
+ return
661
+ }
662
+
663
+ $first = $elements[1]
664
+ switch ($first) {
665
+ 'feature' {
666
+ if ($elements.Count -le 2) {
667
+ __ForgeCompletionResults -Items $featureCommands -Word $wordToComplete
668
+ return
669
+ }
670
+ $sub = $elements[2]
671
+ if ($featureSlugCommands -contains $sub) {
672
+ __ForgeCompletionResults -Items (__ForgeFeatureSlugs) -Word $wordToComplete
673
+ return
674
+ }
675
+ }
676
+ 'mode' {
677
+ __ForgeCompletionResults -Items $modeCommands -Word $wordToComplete
678
+ return
679
+ }
680
+ 'agent' {
681
+ __ForgeCompletionResults -Items $agentCommands -Word $wordToComplete
682
+ return
683
+ }
684
+ 'completion' {
685
+ __ForgeCompletionResults -Items $completionShells -Word $wordToComplete
686
+ return
687
+ }
688
+ default {
689
+ if ($mainSlugCommands -contains $first) {
690
+ __ForgeCompletionResults -Items (__ForgeFeatureSlugs) -Word $wordToComplete
691
+ return
692
+ }
693
+ }
694
+ }
695
+ }
696
+
697
+ # Installation instructions:
698
+ # Option 1 - Add to $PROFILE:
699
+ # forge completion powershell | Out-String | Invoke-Expression
700
+ `;
701
+ }
702
+ }