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.
- package/LICENSE +661 -0
- package/README.md +350 -0
- package/dist/cli.js +306 -0
- package/dist/commands/AbstractCommands.js +16 -0
- package/dist/commands/AgentCommands.js +14 -0
- package/dist/commands/BranchCommands.js +400 -0
- package/dist/commands/CompletionCommands.js +702 -0
- package/dist/commands/EnvCommands.js +56 -0
- package/dist/commands/FeatureCommands.js +4 -0
- package/dist/commands/FixCommands.js +4 -0
- package/dist/commands/InitCommands.js +380 -0
- package/dist/commands/MaintenanceCommands.js +39 -0
- package/dist/commands/ModeCommands.js +15 -0
- package/dist/commands/ProxyCommands.js +14 -0
- package/dist/commands/ReleaseCommands.js +4 -0
- package/dist/commands/ServicesCommands.js +95 -0
- package/dist/commands/SubBranchCommands.js +49 -0
- package/dist/commands/types/InitOptions.js +1 -0
- package/dist/foundation/BranchContext.js +427 -0
- package/dist/foundation/ForgeConfig.js +264 -0
- package/dist/foundation/ForgeConfigFile.js +391 -0
- package/dist/foundation/ForgeContext.js +169 -0
- package/dist/foundation/NpmHelper.js +131 -0
- package/dist/foundation/PathHelper.js +56 -0
- package/dist/foundation/PortAllocator.js +192 -0
- package/dist/foundation/Proxy.js +176 -0
- package/dist/foundation/Repository.js +431 -0
- package/dist/foundation/errors/ForgeError.js +9 -0
- package/dist/foundation/errors/_error.config.js +12 -0
- package/dist/foundation/errors/generated/ForgeBadStateError.js +11 -0
- package/dist/foundation/errors/generated/ForgeConfigError.js +11 -0
- package/dist/foundation/errors/generated/ForgeExpectMainRepositoryError.js +11 -0
- package/dist/foundation/errors/generated/ForgeModeNotDefinedError.js +11 -0
- package/dist/foundation/errors/generated/ForgeNotInActiveBranchError.js +11 -0
- package/dist/foundation/errors/generated/ForgePortAllocationsLoadError.js +11 -0
- package/dist/foundation/errors/generated/ForgePortNotAssignedError.js +11 -0
- package/dist/foundation/errors/generated/ForgePortRangeExhaustedError.js +11 -0
- package/dist/foundation/errors/generated/ForgeServicesScanError.js +11 -0
- package/dist/foundation/errors/generated/ForgeServicesValidationError.js +11 -0
- package/dist/foundation/errors/index.js +13 -0
- package/dist/foundation/types/AIAgent.js +1 -0
- package/dist/foundation/types/AIAgentName.js +11 -0
- package/dist/foundation/types/DeepPartial.js +1 -0
- package/dist/foundation/types/IDE.js +1 -0
- package/dist/foundation/types/IDEName.js +7 -0
- package/dist/foundation/types/ModeConfig.js +1 -0
- package/dist/foundation/types/RepositoryInfos.js +1 -0
- package/dist/foundation/types/Services.js +156 -0
- package/dist/foundation/types/ShellName.js +11 -0
- package/dist/lib/agents.js +47 -0
- package/dist/lib/bootstrap.js +54 -0
- package/dist/lib/branch.js +4 -0
- package/dist/lib/config.js +65 -0
- package/dist/lib/constants.js +13 -0
- package/dist/lib/env.js +20 -0
- package/dist/lib/fs.js +156 -0
- package/dist/lib/git.js +170 -0
- package/dist/lib/hooks.js +98 -0
- package/dist/lib/ide.js +75 -0
- package/dist/lib/merger.js +103 -0
- package/dist/lib/platform.js +13 -0
- package/dist/lib/prompt.js +134 -0
- package/dist/lib/proxy-dashboard.js +75 -0
- package/dist/lib/scanner.js +118 -0
- package/dist/lib/services.js +132 -0
- package/dist/lib/slug.js +35 -0
- package/dist/lib/templates.js +115 -0
- package/dist/lib/validator.js +15 -0
- package/dist/templates/SPEC.md +21 -0
- package/dist/templates/TODO.md +5 -0
- package/dist/templates/agent/001.general.Omnibus.agent.md +4 -0
- package/dist/templates/agent/002.discovery.Inventorius.agent.md +4 -0
- package/dist/templates/agent/003.design.Architecturius.agent.md +8 -0
- package/dist/templates/agent/004.plan.Strategos.agent.md +8 -0
- package/dist/templates/agent/005.tdd.TestDrivenCodificius.agent.md +8 -0
- package/dist/templates/agent/006.code.Codificius.agent.md +8 -0
- package/dist/templates/agent/007.simplify.Consolidarius.agent.md +8 -0
- package/dist/templates/agent/008.review.Auditorix.agent.md +8 -0
- package/dist/templates/agent/009.testwriter.TestScriptor.agent.md +8 -0
- package/dist/templates/agent/010.testexecutor.TestExecutor.agent.md +8 -0
- package/dist/templates/agent/011.commit.Scribus.agent.md +10 -0
- package/dist/templates/agent/CONTEXT.code.md +145 -0
- package/dist/templates/agent/CONTEXT.spec.md +98 -0
- package/dist/templates/agent/Copilot/Code.agent.md +28 -0
- package/dist/templates/agent/Copilot/CodeCommit.agent.md +16 -0
- package/dist/templates/agent/Copilot/Feature-Builder.agent.md +49 -0
- package/dist/templates/agent/Copilot/Reviewer.agent.md +17 -0
- package/dist/templates/agent/Copilot/Simplifier.agent.md +21 -0
- package/dist/templates/agent/Copilot/Specs.agent.md +66 -0
- package/dist/templates/agent/Copilot/SpecsCommit.agent.md +19 -0
- package/dist/templates/agent/Copilot/TODO-Reader.agent.md +18 -0
- package/dist/templates/agent/Copilot/Tester.agent.md +12 -0
- 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
|
+
}
|