feat-forge 1.0.2 → 1.1.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # FeatForge
2
2
 
3
+ [![CI](https://github.com/lixus3d/feat-forge-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/lixus3d/feat-forge-cli/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/feat-forge)](https://www.npmjs.com/package/feat-forge)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
6
+ [![Vitest](https://img.shields.io/badge/tested%20with-Vitest-6E9F18?logo=vitest&logoColor=white)](https://vitest.dev/)
7
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/lixus3d/feat-forge-cli/pulls)
8
+
3
9
  **FeatForge** is a feature-first workflow and CLI (`forge`) to help you build software at scale, with (or without) AI agents.
4
10
 
5
11
  Its goal is to make the specification of features explicit and separate the thinking/specifying phase from the coding/implementation phase, across multiple agents and repositories, while keeping everything organized and traceable.
@@ -348,3 +354,7 @@ For all commands and options:
348
354
  ```bash
349
355
  forge --help
350
356
  ```
357
+
358
+ # License
359
+
360
+ [![License: AGPL-3.0](https://img.shields.io/npm/l/feat-forge)](./LICENSE)
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import 'reflect-metadata';
4
4
  import { Command } from 'commander';
5
5
  import { AgentCommands } from './commands/AgentCommands.js';
6
6
  import { MaintenanceCommands } from './commands/MaintenanceCommands.js';
7
+ import { AliasCommands } from './commands/AliasCommands.js';
7
8
  import { CompletionCommands } from './commands/CompletionCommands.js';
8
9
  import { FeatureCommands } from './commands/FeatureCommands.js';
9
10
  import { InitCommands } from './commands/InitCommands.js';
@@ -91,6 +92,11 @@ function genericBranchTypeCommands(baseCommand, handlers, subType = null) {
91
92
  .argument(`[${argName}]`, argDesc)
92
93
  .description(`Open the given ${subType ? subType + ' ' : ''}branch in the configured IDE (if no ${subType ? subType + ' ' : ''}branch is given, opens the nearest ${subType ? subType + ' ' : ''}branch)`)
93
94
  .action(handlers.open.bind(handlers));
95
+ baseCommand
96
+ .command('path')
97
+ .argument(`[${argName}]`, argDesc)
98
+ .description(`Print the worktree path of a ${subType ? subType + ' ' : ''}branch (useful for shell integration: cd "$(forge path <name>)")`)
99
+ .action(handlers.path.bind(handlers));
94
100
  baseCommand
95
101
  .command('merge')
96
102
  .argument(`<${argName}>`, argDesc)
@@ -221,6 +227,29 @@ function registerProxyCommands(program, context) {
221
227
  await handlers.start(options);
222
228
  });
223
229
  }
230
+ /*
231
+ * Register alias commands with the CLI.
232
+ * This command works without config.
233
+ */
234
+ function registerAliasCommands(program) {
235
+ const handlers = new AliasCommands();
236
+ const validShells = [ShellName.Bash, ShellName.Zsh, ShellName.Fish, ShellName.PowerShell, ShellName.Pwsh];
237
+ function isValidShellName(value) {
238
+ return validShells.includes(value);
239
+ }
240
+ program
241
+ .command('alias <shell>')
242
+ .description(`Generate shell aliases for feat-forge (${validShells.join(', ')})`)
243
+ .option('--prefix <prefix>', 'Prefix for alias names', 'ff')
244
+ .action((shell, options) => {
245
+ if (!isValidShellName(shell)) {
246
+ console.error(`Error: Unsupported shell type "${shell}". Supported shells: ${validShells.join(', ')}`);
247
+ process.exitCode = 1;
248
+ return;
249
+ }
250
+ handlers.generate(shell, options.prefix);
251
+ });
252
+ }
224
253
  /*
225
254
  * Register completion commands with the CLI.
226
255
  * This command works both with and without config.
@@ -263,13 +292,16 @@ async function main() {
263
292
  program.name('forge').description('Feat-Forge workflow CLI').version(packageJson.version);
264
293
  // Init command doesn't need config
265
294
  registerInitCommands(program);
295
+ // Alias command doesn't need config
296
+ registerAliasCommands(program);
266
297
  // Completion command should work with or without config
267
298
  // Register it early so it's available even without .feat-forge.json
268
299
  let context;
269
300
  // Load config for other commands
270
301
  const isInitCommand = process.argv[2] === 'init';
302
+ const isAliasCommand = process.argv[2] === 'alias';
271
303
  const isCompletionCommand = process.argv[2] === 'completion';
272
- if (!isInitCommand) {
304
+ if (!isInitCommand && !isAliasCommand) {
273
305
  try {
274
306
  context = await loadForgeContext();
275
307
  }
@@ -0,0 +1,245 @@
1
+ import { ShellName } from '../foundation/types/ShellName.js';
2
+ /**
3
+ * Commands for generating shell aliases for feat-forge.
4
+ */
5
+ export class AliasCommands {
6
+ /**
7
+ * Generate and print shell aliases for the specified shell.
8
+ */
9
+ generate(shell, prefix) {
10
+ let output;
11
+ switch (shell) {
12
+ case ShellName.Bash:
13
+ output = this.generateBashAliases(prefix);
14
+ break;
15
+ case ShellName.Zsh:
16
+ output = this.generateZshAliases(prefix);
17
+ break;
18
+ case ShellName.Fish:
19
+ output = this.generateFishAliases(prefix);
20
+ break;
21
+ case ShellName.PowerShell:
22
+ case ShellName.Pwsh:
23
+ output = this.generatePowerShellAliases(prefix);
24
+ break;
25
+ default:
26
+ throw new Error(`Unsupported shell: ${shell}`);
27
+ }
28
+ console.log(output);
29
+ }
30
+ // ============================================================================
31
+ // PRIVATE METHODS
32
+ // ============================================================================
33
+ generateBashAliases(p) {
34
+ return `# feat-forge aliases (generated by: forge alias bash --prefix ${p})
35
+ # Add to your ~/.bashrc: source <(forge alias bash${p !== 'ff' ? ` --prefix ${p}` : ''})
36
+
37
+ # Group aliases
38
+ alias ${p}='forge'
39
+ alias ${p}f='forge feature'
40
+ alias ${p}i='forge fix'
41
+ alias ${p}r='forge release'
42
+
43
+ # Command shortcuts
44
+ alias ${p}s='forge start'
45
+ alias ${p}x='forge stop'
46
+ alias ${p}c='forge create'
47
+ alias ${p}o='forge open'
48
+ alias ${p}l='forge list'
49
+ alias ${p}m='forge merge'
50
+ alias ${p}a='forge archive'
51
+
52
+ # Feature shortcuts
53
+ alias ${p}fs='forge feature start'
54
+ alias ${p}fx='forge feature stop'
55
+ alias ${p}fc='forge feature create'
56
+ alias ${p}fo='forge feature open'
57
+ alias ${p}fl='forge feature list'
58
+ alias ${p}fm='forge feature merge'
59
+ alias ${p}fa='forge feature archive'
60
+
61
+ # Fix shortcuts
62
+ alias ${p}is='forge fix start'
63
+ alias ${p}ix='forge fix stop'
64
+ alias ${p}ic='forge fix create'
65
+ alias ${p}io='forge fix open'
66
+ alias ${p}il='forge fix list'
67
+ alias ${p}im='forge fix merge'
68
+ alias ${p}ia='forge fix archive'
69
+
70
+ # Release shortcuts
71
+ alias ${p}rs='forge release start'
72
+ alias ${p}rx='forge release stop'
73
+ alias ${p}rc='forge release create'
74
+ alias ${p}ro='forge release open'
75
+ alias ${p}rl='forge release list'
76
+ alias ${p}rm='forge release merge'
77
+ alias ${p}ra='forge release archive'
78
+
79
+ # cd into branch worktree
80
+ ${p}cd() { cd "$(forge path "$@")"; }
81
+ ${p}fcd() { cd "$(forge feature path "$@")"; }
82
+ ${p}icd() { cd "$(forge fix path "$@")"; }
83
+ ${p}rcd() { cd "$(forge release path "$@")"; }
84
+ `;
85
+ }
86
+ generateZshAliases(p) {
87
+ return `# feat-forge aliases (generated by: forge alias zsh --prefix ${p})
88
+ # Add to your ~/.zshrc: source <(forge alias zsh${p !== 'ff' ? ` --prefix ${p}` : ''})
89
+
90
+ # Group aliases
91
+ alias ${p}='forge'
92
+ alias ${p}f='forge feature'
93
+ alias ${p}i='forge fix'
94
+ alias ${p}r='forge release'
95
+
96
+ # Command shortcuts
97
+ alias ${p}s='forge start'
98
+ alias ${p}x='forge stop'
99
+ alias ${p}c='forge create'
100
+ alias ${p}o='forge open'
101
+ alias ${p}l='forge list'
102
+ alias ${p}m='forge merge'
103
+ alias ${p}a='forge archive'
104
+
105
+ # Feature shortcuts
106
+ alias ${p}fs='forge feature start'
107
+ alias ${p}fx='forge feature stop'
108
+ alias ${p}fc='forge feature create'
109
+ alias ${p}fo='forge feature open'
110
+ alias ${p}fl='forge feature list'
111
+ alias ${p}fm='forge feature merge'
112
+ alias ${p}fa='forge feature archive'
113
+
114
+ # Fix shortcuts
115
+ alias ${p}is='forge fix start'
116
+ alias ${p}ix='forge fix stop'
117
+ alias ${p}ic='forge fix create'
118
+ alias ${p}io='forge fix open'
119
+ alias ${p}il='forge fix list'
120
+ alias ${p}im='forge fix merge'
121
+ alias ${p}ia='forge fix archive'
122
+
123
+ # Release shortcuts
124
+ alias ${p}rs='forge release start'
125
+ alias ${p}rx='forge release stop'
126
+ alias ${p}rc='forge release create'
127
+ alias ${p}ro='forge release open'
128
+ alias ${p}rl='forge release list'
129
+ alias ${p}rm='forge release merge'
130
+ alias ${p}ra='forge release archive'
131
+
132
+ # cd into branch worktree
133
+ ${p}cd() { cd "$(forge path "$@")"; }
134
+ ${p}fcd() { cd "$(forge feature path "$@")"; }
135
+ ${p}icd() { cd "$(forge fix path "$@")"; }
136
+ ${p}rcd() { cd "$(forge release path "$@")"; }
137
+ `;
138
+ }
139
+ generateFishAliases(p) {
140
+ return `# feat-forge aliases (generated by: forge alias fish --prefix ${p})
141
+ # Add to your ~/.config/fish/config.fish: forge alias fish${p !== 'ff' ? ` --prefix ${p}` : ''} | source
142
+
143
+ # Group aliases
144
+ abbr -a ${p} forge
145
+ abbr -a ${p}f 'forge feature'
146
+ abbr -a ${p}i 'forge fix'
147
+ abbr -a ${p}r 'forge release'
148
+
149
+ # Command shortcuts
150
+ abbr -a ${p}s 'forge start'
151
+ abbr -a ${p}x 'forge stop'
152
+ abbr -a ${p}c 'forge create'
153
+ abbr -a ${p}o 'forge open'
154
+ abbr -a ${p}l 'forge list'
155
+ abbr -a ${p}m 'forge merge'
156
+ abbr -a ${p}a 'forge archive'
157
+
158
+ # Feature shortcuts
159
+ abbr -a ${p}fs 'forge feature start'
160
+ abbr -a ${p}fx 'forge feature stop'
161
+ abbr -a ${p}fc 'forge feature create'
162
+ abbr -a ${p}fo 'forge feature open'
163
+ abbr -a ${p}fl 'forge feature list'
164
+ abbr -a ${p}fm 'forge feature merge'
165
+ abbr -a ${p}fa 'forge feature archive'
166
+
167
+ # Fix shortcuts
168
+ abbr -a ${p}is 'forge fix start'
169
+ abbr -a ${p}ix 'forge fix stop'
170
+ abbr -a ${p}ic 'forge fix create'
171
+ abbr -a ${p}io 'forge fix open'
172
+ abbr -a ${p}il 'forge fix list'
173
+ abbr -a ${p}im 'forge fix merge'
174
+ abbr -a ${p}ia 'forge fix archive'
175
+
176
+ # Release shortcuts
177
+ abbr -a ${p}rs 'forge release start'
178
+ abbr -a ${p}rx 'forge release stop'
179
+ abbr -a ${p}rc 'forge release create'
180
+ abbr -a ${p}ro 'forge release open'
181
+ abbr -a ${p}rl 'forge release list'
182
+ abbr -a ${p}rm 'forge release merge'
183
+ abbr -a ${p}ra 'forge release archive'
184
+
185
+ # cd into branch worktree
186
+ function ${p}cd; cd (forge path $argv); end
187
+ function ${p}fcd; cd (forge feature path $argv); end
188
+ function ${p}icd; cd (forge fix path $argv); end
189
+ function ${p}rcd; cd (forge release path $argv); end
190
+ `;
191
+ }
192
+ generatePowerShellAliases(p) {
193
+ return `# feat-forge aliases (generated by: forge alias powershell --prefix ${p})
194
+ # Add to your $PROFILE: forge alias powershell${p !== 'ff' ? ` --prefix ${p}` : ''} | Out-String | Invoke-Expression
195
+
196
+ # Group functions
197
+ function ${p} { forge @args }
198
+ function ${p}f { forge feature @args }
199
+ function ${p}i { forge fix @args }
200
+ function ${p}r { forge release @args }
201
+
202
+ # Command shortcuts
203
+ function ${p}s { forge start @args }
204
+ function ${p}x { forge stop @args }
205
+ function ${p}c { forge create @args }
206
+ function ${p}o { forge open @args }
207
+ function ${p}l { forge list @args }
208
+ function ${p}m { forge merge @args }
209
+ function ${p}a { forge archive @args }
210
+
211
+ # Feature shortcuts
212
+ function ${p}fs { forge feature start @args }
213
+ function ${p}fx { forge feature stop @args }
214
+ function ${p}fc { forge feature create @args }
215
+ function ${p}fo { forge feature open @args }
216
+ function ${p}fl { forge feature list @args }
217
+ function ${p}fm { forge feature merge @args }
218
+ function ${p}fa { forge feature archive @args }
219
+
220
+ # Fix shortcuts
221
+ function ${p}is { forge fix start @args }
222
+ function ${p}ix { forge fix stop @args }
223
+ function ${p}ic { forge fix create @args }
224
+ function ${p}io { forge fix open @args }
225
+ function ${p}il { forge fix list @args }
226
+ function ${p}im { forge fix merge @args }
227
+ function ${p}ia { forge fix archive @args }
228
+
229
+ # Release shortcuts
230
+ function ${p}rs { forge release start @args }
231
+ function ${p}rx { forge release stop @args }
232
+ function ${p}rc { forge release create @args }
233
+ function ${p}ro { forge release open @args }
234
+ function ${p}rl { forge release list @args }
235
+ function ${p}rm { forge release merge @args }
236
+ function ${p}ra { forge release archive @args }
237
+
238
+ # cd into branch worktree
239
+ function ${p}cd { Set-Location (forge path @args) }
240
+ function ${p}fcd { Set-Location (forge feature path @args) }
241
+ function ${p}icd { Set-Location (forge fix path @args) }
242
+ function ${p}rcd { Set-Location (forge release path @args) }
243
+ `;
244
+ }
245
+ }
@@ -192,6 +192,18 @@ export class BranchCommands extends AbstractCommands {
192
192
  process.exitCode = 1;
193
193
  });
194
194
  }
195
+ /**
196
+ * Print the path of a branch worktree directory.
197
+ * Outputs only the path to stdout, suitable for shell capture: cd "$(forge path <branch>)"
198
+ *
199
+ * @param rawSlug - Optional branch slug. If not provided, finds the nearest branch context.
200
+ */
201
+ async path(rawSlug) {
202
+ const branchContext = rawSlug
203
+ ? await this.context.loadBranchContext(await confirmSlugOrThrow(rawSlug))
204
+ : await BranchContext.findNearestBranchContext(this.context);
205
+ process.stdout.write(branchContext.path);
206
+ }
195
207
  async merge(rawSlug) {
196
208
  const branchName = await confirmSlugOrThrow(rawSlug);
197
209
  let targetRepos;