vibe-forge 0.8.1 → 0.8.2

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 (51) hide show
  1. package/.claude/commands/configure-vcs.md +102 -102
  2. package/.claude/commands/forge.md +218 -218
  3. package/.claude/hooks/worker-loop.js +220 -217
  4. package/.claude/settings.json +89 -89
  5. package/README.md +149 -191
  6. package/agents/aegis/personality.md +303 -303
  7. package/agents/anvil/personality.md +278 -278
  8. package/agents/architect/personality.md +260 -260
  9. package/agents/crucible/personality.md +362 -362
  10. package/agents/crucible-x/personality.md +210 -210
  11. package/agents/ember/personality.md +293 -293
  12. package/agents/flux/personality.md +248 -248
  13. package/agents/furnace/personality.md +342 -342
  14. package/agents/herald/personality.md +249 -249
  15. package/agents/oracle/personality.md +284 -284
  16. package/agents/pixel/personality.md +140 -140
  17. package/agents/planning-hub/personality.md +473 -473
  18. package/agents/scribe/personality.md +253 -253
  19. package/agents/slag/personality.md +268 -268
  20. package/agents/temper/personality.md +270 -270
  21. package/bin/cli.js +372 -372
  22. package/bin/forge-daemon.sh +477 -477
  23. package/bin/forge-setup.sh +662 -661
  24. package/bin/forge-spawn.sh +164 -164
  25. package/bin/forge.sh +566 -566
  26. package/docs/commands.md +8 -8
  27. package/package.json +77 -77
  28. package/{bin → src}/lib/agents.sh +177 -177
  29. package/{bin → src}/lib/check-aliases.js +50 -50
  30. package/{bin → src}/lib/colors.sh +45 -44
  31. package/{bin → src}/lib/config.sh +347 -347
  32. package/{bin → src}/lib/constants.sh +241 -241
  33. package/{bin → src}/lib/daemon/budgets.sh +107 -107
  34. package/{bin → src}/lib/daemon/dependencies.sh +146 -146
  35. package/{bin → src}/lib/daemon/display.sh +128 -128
  36. package/{bin → src}/lib/daemon/notifications.sh +273 -273
  37. package/{bin → src}/lib/daemon/routing.sh +93 -93
  38. package/{bin → src}/lib/daemon/state.sh +163 -163
  39. package/{bin → src}/lib/daemon/sync.sh +103 -103
  40. package/{bin → src}/lib/database.sh +357 -357
  41. package/{bin → src}/lib/frontmatter.js +106 -106
  42. package/{bin → src}/lib/heimdall-setup.js +113 -113
  43. package/{bin → src}/lib/heimdall.js +265 -265
  44. package/src/lib/index.sh +25 -0
  45. package/{bin → src}/lib/json.sh +264 -264
  46. package/{bin → src}/lib/terminal.js +452 -452
  47. package/{bin → src}/lib/util.sh +126 -126
  48. package/{bin → src}/lib/vcs.js +349 -349
  49. package/{context → templates}/project-context-template.md +122 -122
  50. package/config/task-template.md +0 -159
  51. package/config/templates/handoff-template.md +0 -40
package/bin/cli.js CHANGED
@@ -1,372 +1,372 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Vibe Forge CLI
5
- *
6
- * Usage:
7
- * npx @sugar-crash-studios/vibe-forge init Initialize Forge in current project
8
- * npx @sugar-crash-studios/vibe-forge update Update Forge to latest version
9
- * npx @sugar-crash-studios/vibe-forge --help Show help
10
- */
11
-
12
- const { execSync, spawn } = require('child_process');
13
- const fs = require('fs');
14
- const path = require('path');
15
- const os = require('os');
16
-
17
- // Read version from package.json (single source of truth)
18
- const packageJson = require(path.join(__dirname, '..', 'package.json'));
19
- const VERSION = packageJson.version;
20
- const REPO_URL = 'https://github.com/sugar-crash-studios/vibe-forge.git';
21
- const FORGE_DIR = '_vibe-forge';
22
-
23
- // Colors for terminal output
24
- // NOTE: Intentionally duplicated from bin/lib/colors.sh
25
- // This is by design: cli.js runs standalone via npx before the rest of Vibe Forge
26
- // is installed, so it cannot import colors.sh. If you update colors here,
27
- // also update bin/lib/colors.sh to keep them in sync.
28
- const colors = {
29
- reset: '\x1b[0m',
30
- red: '\x1b[31m',
31
- green: '\x1b[32m',
32
- yellow: '\x1b[33m',
33
- blue: '\x1b[34m',
34
- cyan: '\x1b[36m',
35
- };
36
-
37
- function log(message, color = colors.reset) {
38
- console.log(`${color}${message}${colors.reset}`);
39
- }
40
-
41
- function logError(message) {
42
- log(`Error: ${message}`, colors.red);
43
- }
44
-
45
- function logSuccess(message) {
46
- log(message, colors.green);
47
- }
48
-
49
- function logInfo(message) {
50
- log(message, colors.blue);
51
- }
52
-
53
- function showBanner() {
54
- console.log(`
55
- ${colors.yellow}🔥 Vibe Forge${colors.reset}
56
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
57
- Multi-agent development orchestration
58
- `);
59
- }
60
-
61
- function showHelp() {
62
- showBanner();
63
- console.log(`Usage: npx @sugar-crash-studios/vibe-forge <command>
64
-
65
- Commands:
66
- init Initialize Vibe Forge in the current project
67
- update Update Vibe Forge to the latest version
68
- agents List all agents, roles, and aliases
69
- version Show version information
70
- help Show this help message
71
-
72
- Examples:
73
- npx @sugar-crash-studios/vibe-forge init # Set up Forge in your project
74
- npx @sugar-crash-studios/vibe-forge update # Update to latest version
75
- npx @sugar-crash-studios/vibe-forge agents # Show agent roster
76
- `);
77
- }
78
-
79
- function showAgents() {
80
- const agentsFile = path.join(__dirname, '..', 'config', 'agents.json');
81
- let agents = {};
82
- try {
83
- const data = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
84
- agents = data.agents || {};
85
- } catch (e) {
86
- logError('Could not read agents.json');
87
- logInfo('Run from inside a Vibe Forge installation, or run: npx @sugar-crash-studios/vibe-forge init');
88
- return;
89
- }
90
-
91
- showBanner();
92
- console.log(`${colors.yellow}Agent Roster${colors.reset}`);
93
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
94
- console.log('');
95
-
96
- const typeOrder = ['core', 'worker', 'specialist', 'advisor'];
97
- const typeLabels = {
98
- core: 'Core (Always Running)',
99
- worker: 'Workers (Per-Task)',
100
- specialist: 'Specialists (On-Demand)',
101
- advisor: 'Advisors',
102
- };
103
-
104
- for (const type of typeOrder) {
105
- const group = Object.entries(agents).filter(([, a]) => a.type === type);
106
- if (group.length === 0) continue;
107
- console.log(` ${colors.cyan}${typeLabels[type] || type}${colors.reset}`);
108
- for (const [name, agent] of group) {
109
- console.log(` ${agent.icon || ' '} ${colors.yellow}${name}${colors.reset} — ${agent.role}`);
110
- if (agent.aliases && agent.aliases.length > 0) {
111
- console.log(` aliases: ${agent.aliases.join(', ')}`);
112
- }
113
- }
114
- console.log('');
115
- }
116
-
117
- console.log(`Spawn an agent:`);
118
- console.log(` ${colors.yellow}./bin/forge.sh spawn <agent-name-or-alias>${colors.reset}`);
119
- console.log('');
120
- }
121
-
122
- function showVersion() {
123
- console.log(`Vibe Forge v${VERSION}`);
124
- }
125
-
126
- function checkPrerequisites() {
127
- // Check for git
128
- try {
129
- execSync('git --version', { stdio: 'pipe' });
130
- } catch {
131
- logError('Git is not installed. Please install Git first.');
132
- logInfo(' Windows: winget install Git.Git');
133
- logInfo(' macOS: brew install git');
134
- logInfo(' Linux: sudo apt install git');
135
- process.exit(1);
136
- }
137
-
138
- // Check for Claude Code
139
- try {
140
- execSync('claude --version', { stdio: 'pipe' });
141
- } catch {
142
- logError('Claude Code CLI is not installed.');
143
- logInfo(' Install from: https://claude.ai/download');
144
- process.exit(1);
145
- }
146
- }
147
-
148
- function isWindows() {
149
- return os.platform() === 'win32';
150
- }
151
-
152
- function getBashPath() {
153
- if (!isWindows()) {
154
- return 'bash';
155
- }
156
- // Delegate to shared terminal utility for Windows Git Bash detection
157
- const { findGitBash } = require('./lib/terminal.js');
158
- return findGitBash() || null;
159
- }
160
-
161
- function runBashScript(scriptPath, args = []) {
162
- const bashPath = getBashPath();
163
-
164
- if (!bashPath) {
165
- logError('Could not find bash. Please install Git Bash on Windows.');
166
- process.exit(1);
167
- }
168
-
169
- return new Promise((resolve, reject) => {
170
- const child = spawn(bashPath, [scriptPath, ...args], {
171
- stdio: 'inherit',
172
- cwd: process.cwd(),
173
- env: {
174
- ...process.env,
175
- CLAUDE_CODE_GIT_BASH_PATH: bashPath,
176
- },
177
- });
178
-
179
- child.on('close', (code) => {
180
- if (code === 0) {
181
- resolve();
182
- } else {
183
- reject(new Error(`Script exited with code ${code}`));
184
- }
185
- });
186
-
187
- child.on('error', (err) => {
188
- reject(err);
189
- });
190
- });
191
- }
192
-
193
- async function initCommand() {
194
- showBanner();
195
-
196
- const targetDir = path.join(process.cwd(), FORGE_DIR);
197
-
198
- // Check if already initialized
199
- if (fs.existsSync(targetDir)) {
200
- logInfo(`Vibe Forge already exists at ${FORGE_DIR}/`);
201
- log('');
202
- log('To update, run: npx @sugar-crash-studios/vibe-forge update');
203
- log('To reinitialize, delete the _vibe-forge folder first.');
204
- return;
205
- }
206
-
207
- logInfo('Checking prerequisites...');
208
- checkPrerequisites();
209
- logSuccess('Prerequisites OK');
210
- log('');
211
-
212
- // Clone the repository
213
- logInfo(`Cloning Vibe Forge into ${FORGE_DIR}/...`);
214
- try {
215
- execSync(`git clone --depth 1 ${REPO_URL} ${FORGE_DIR}`, {
216
- stdio: 'inherit',
217
- cwd: process.cwd(),
218
- });
219
- logSuccess('Clone complete');
220
- } catch {
221
- logError('Failed to clone repository');
222
- process.exit(1);
223
- }
224
-
225
- log('');
226
-
227
- // Validate agents.json (alias collisions)
228
- validateAgentsConfig(targetDir);
229
-
230
- // Update project's .gitignore to ignore tool internals but keep project data
231
- updateGitignore();
232
-
233
- // Run the setup script
234
- logInfo('Running setup...');
235
- log('');
236
-
237
- try {
238
- const setupScript = path.join(targetDir, 'bin', 'forge-setup.sh');
239
- await runBashScript(setupScript);
240
- } catch (err) {
241
- logError(`Setup failed: ${err.message}`);
242
- process.exit(1);
243
- }
244
- }
245
-
246
- function validateAgentsConfig(forgeDir) {
247
- const checkScript = path.join(forgeDir, 'bin', 'lib', 'check-aliases.js');
248
- if (!fs.existsSync(checkScript)) return;
249
-
250
- try {
251
- const agentsFile = path.join(forgeDir, 'config', 'agents.json');
252
- execSync(`node "${checkScript}" "${agentsFile}"`, { stdio: 'pipe' });
253
- const config = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
254
- const agentCount = Object.keys(config.agents || {}).length;
255
- logSuccess(`Agent config valid (${agentCount} agents, no alias collisions)`);
256
- } catch (err) {
257
- logError('Agent alias collisions detected in config/agents.json');
258
- logInfo(err.stderr ? err.stderr.toString().trim() : 'Run node bin/lib/check-aliases.js for details');
259
- logError('Fix collisions before using Vibe Forge.');
260
- process.exit(1);
261
- }
262
- }
263
-
264
- function updateGitignore() {
265
- const gitignorePath = path.join(process.cwd(), '.gitignore');
266
- const forgeIgnoreMarker = '# Vibe Forge';
267
- const forgeIgnoreBlock = `
268
- # Vibe Forge
269
- # Tool internals (regenerated on update)
270
- _vibe-forge/.git/
271
- _vibe-forge/bin/
272
- _vibe-forge/agents/
273
- _vibe-forge/config/
274
- _vibe-forge/docs/
275
- _vibe-forge/tests/
276
- _vibe-forge/src/
277
- _vibe-forge/node_modules/
278
- _vibe-forge/package*.json
279
- _vibe-forge/*.md
280
- _vibe-forge/LICENSE
281
- _vibe-forge/.github/
282
-
283
- # Keep project data (tasks, context) - these ARE committed
284
- # !_vibe-forge/tasks/
285
- # !_vibe-forge/context/
286
- # !_vibe-forge/.claude/
287
- `;
288
-
289
- try {
290
- let content = '';
291
- if (fs.existsSync(gitignorePath)) {
292
- content = fs.readFileSync(gitignorePath, 'utf8');
293
- // Check if already has forge entries
294
- if (content.includes(forgeIgnoreMarker)) {
295
- return; // Already configured
296
- }
297
- }
298
-
299
- // Append forge ignore block
300
- const newContent = content.trimEnd() + '\n' + forgeIgnoreBlock;
301
- fs.writeFileSync(gitignorePath, newContent);
302
- logSuccess('Updated .gitignore for Vibe Forge');
303
- } catch (err) {
304
- logError(`Warning: Could not update .gitignore: ${err.message}`);
305
- }
306
- }
307
-
308
- async function updateCommand() {
309
- showBanner();
310
-
311
- const targetDir = path.join(process.cwd(), FORGE_DIR);
312
-
313
- // Check if initialized
314
- if (!fs.existsSync(targetDir)) {
315
- logError('Vibe Forge is not initialized in this project.');
316
- log('');
317
- log('Run: npx @sugar-crash-studios/vibe-forge init');
318
- process.exit(1);
319
- }
320
-
321
- logInfo('Updating Vibe Forge...');
322
-
323
- try {
324
- // Fetch and reset to origin/main - works even without tracking branch
325
- execSync('git fetch origin', {
326
- stdio: 'inherit',
327
- cwd: targetDir,
328
- });
329
- execSync('git reset --hard origin/main', {
330
- stdio: 'inherit',
331
- cwd: targetDir,
332
- });
333
- logSuccess('Update complete');
334
- } catch {
335
- logError('Failed to update. Check your network connection or try deleting _vibe-forge and running init again.');
336
- process.exit(1);
337
- }
338
- }
339
-
340
- // Main entry point
341
- async function main() {
342
- const args = process.argv.slice(2);
343
- const command = args[0] || 'help';
344
-
345
- switch (command) {
346
- case 'init':
347
- await initCommand();
348
- break;
349
- case 'update':
350
- await updateCommand();
351
- break;
352
- case 'agents':
353
- showAgents();
354
- break;
355
- case 'version':
356
- case '--version':
357
- case '-v':
358
- showVersion();
359
- break;
360
- case 'help':
361
- case '--help':
362
- case '-h':
363
- default:
364
- showHelp();
365
- break;
366
- }
367
- }
368
-
369
- main().catch((err) => {
370
- logError(err.message);
371
- process.exit(1);
372
- });
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Vibe Forge CLI
5
+ *
6
+ * Usage:
7
+ * npx vibe-forge init Initialize Forge in current project
8
+ * npx vibe-forge update Update Forge to latest version
9
+ * npx vibe-forge --help Show help
10
+ */
11
+
12
+ const { execSync, spawn } = require('child_process');
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const os = require('os');
16
+
17
+ // Read version from package.json (single source of truth)
18
+ const packageJson = require(path.join(__dirname, '..', 'package.json'));
19
+ const VERSION = packageJson.version;
20
+ const REPO_URL = 'https://github.com/sugar-crash-studios/vibe-forge.git';
21
+ const FORGE_DIR = '_vibe-forge';
22
+
23
+ // Colors for terminal output
24
+ // NOTE: Intentionally duplicated from src/lib/colors.sh
25
+ // This is by design: cli.js runs standalone via npx before the rest of Vibe Forge
26
+ // is installed, so it cannot import colors.sh. If you update colors here,
27
+ // also update src/lib/colors.sh to keep them in sync.
28
+ const colors = {
29
+ reset: '\x1b[0m',
30
+ red: '\x1b[31m',
31
+ green: '\x1b[32m',
32
+ yellow: '\x1b[33m',
33
+ blue: '\x1b[34m',
34
+ cyan: '\x1b[36m',
35
+ };
36
+
37
+ function log(message, color = colors.reset) {
38
+ console.log(`${color}${message}${colors.reset}`);
39
+ }
40
+
41
+ function logError(message) {
42
+ log(`Error: ${message}`, colors.red);
43
+ }
44
+
45
+ function logSuccess(message) {
46
+ log(message, colors.green);
47
+ }
48
+
49
+ function logInfo(message) {
50
+ log(message, colors.blue);
51
+ }
52
+
53
+ function showBanner() {
54
+ console.log(`
55
+ ${colors.yellow}🔥 Vibe Forge${colors.reset}
56
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
57
+ Multi-agent development orchestration
58
+ `);
59
+ }
60
+
61
+ function showHelp() {
62
+ showBanner();
63
+ console.log(`Usage: npx vibe-forge <command>
64
+
65
+ Commands:
66
+ init Initialize Vibe Forge in the current project
67
+ update Update Vibe Forge to the latest version
68
+ agents List all agents, roles, and aliases
69
+ version Show version information
70
+ help Show this help message
71
+
72
+ Examples:
73
+ npx vibe-forge init # Set up Forge in your project
74
+ npx vibe-forge update # Update to latest version
75
+ npx vibe-forge agents # Show agent roster
76
+ `);
77
+ }
78
+
79
+ function showAgents() {
80
+ const agentsFile = path.join(__dirname, '..', 'config', 'agents.json');
81
+ let agents = {};
82
+ try {
83
+ const data = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
84
+ agents = data.agents || {};
85
+ } catch (e) {
86
+ logError('Could not read agents.json');
87
+ logInfo('Run from inside a Vibe Forge installation, or run: npx vibe-forge init');
88
+ return;
89
+ }
90
+
91
+ showBanner();
92
+ console.log(`${colors.yellow}Agent Roster${colors.reset}`);
93
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
94
+ console.log('');
95
+
96
+ const typeOrder = ['core', 'worker', 'specialist', 'advisor'];
97
+ const typeLabels = {
98
+ core: 'Core (Always Running)',
99
+ worker: 'Workers (Per-Task)',
100
+ specialist: 'Specialists (On-Demand)',
101
+ advisor: 'Advisors',
102
+ };
103
+
104
+ for (const type of typeOrder) {
105
+ const group = Object.entries(agents).filter(([, a]) => a.type === type);
106
+ if (group.length === 0) continue;
107
+ console.log(` ${colors.cyan}${typeLabels[type] || type}${colors.reset}`);
108
+ for (const [name, agent] of group) {
109
+ console.log(` ${agent.icon || ' '} ${colors.yellow}${name}${colors.reset} — ${agent.role}`);
110
+ if (agent.aliases && agent.aliases.length > 0) {
111
+ console.log(` aliases: ${agent.aliases.join(', ')}`);
112
+ }
113
+ }
114
+ console.log('');
115
+ }
116
+
117
+ console.log(`Spawn an agent:`);
118
+ console.log(` ${colors.yellow}./bin/forge.sh spawn <agent-name-or-alias>${colors.reset}`);
119
+ console.log('');
120
+ }
121
+
122
+ function showVersion() {
123
+ console.log(`Vibe Forge v${VERSION}`);
124
+ }
125
+
126
+ function checkPrerequisites() {
127
+ // Check for git
128
+ try {
129
+ execSync('git --version', { stdio: 'pipe' });
130
+ } catch {
131
+ logError('Git is not installed. Please install Git first.');
132
+ logInfo(' Windows: winget install Git.Git');
133
+ logInfo(' macOS: brew install git');
134
+ logInfo(' Linux: sudo apt install git');
135
+ process.exit(1);
136
+ }
137
+
138
+ // Check for Claude Code
139
+ try {
140
+ execSync('claude --version', { stdio: 'pipe' });
141
+ } catch {
142
+ logError('Claude Code CLI is not installed.');
143
+ logInfo(' Install from: https://claude.ai/download');
144
+ process.exit(1);
145
+ }
146
+ }
147
+
148
+ function isWindows() {
149
+ return os.platform() === 'win32';
150
+ }
151
+
152
+ function getBashPath() {
153
+ if (!isWindows()) {
154
+ return 'bash';
155
+ }
156
+ // Delegate to shared terminal utility for Windows Git Bash detection
157
+ const { findGitBash } = require('../src/lib/terminal.js');
158
+ return findGitBash() || null;
159
+ }
160
+
161
+ function runBashScript(scriptPath, args = []) {
162
+ const bashPath = getBashPath();
163
+
164
+ if (!bashPath) {
165
+ logError('Could not find bash. Please install Git Bash on Windows.');
166
+ process.exit(1);
167
+ }
168
+
169
+ return new Promise((resolve, reject) => {
170
+ const child = spawn(bashPath, [scriptPath, ...args], {
171
+ stdio: 'inherit',
172
+ cwd: process.cwd(),
173
+ env: {
174
+ ...process.env,
175
+ CLAUDE_CODE_GIT_BASH_PATH: bashPath,
176
+ },
177
+ });
178
+
179
+ child.on('close', (code) => {
180
+ if (code === 0) {
181
+ resolve();
182
+ } else {
183
+ reject(new Error(`Script exited with code ${code}`));
184
+ }
185
+ });
186
+
187
+ child.on('error', (err) => {
188
+ reject(err);
189
+ });
190
+ });
191
+ }
192
+
193
+ async function initCommand() {
194
+ showBanner();
195
+
196
+ const targetDir = path.join(process.cwd(), FORGE_DIR);
197
+
198
+ // Check if already initialized
199
+ if (fs.existsSync(targetDir)) {
200
+ logInfo(`Vibe Forge already exists at ${FORGE_DIR}/`);
201
+ log('');
202
+ log('To update, run: npx vibe-forge update');
203
+ log('To reinitialize, delete the _vibe-forge folder first.');
204
+ return;
205
+ }
206
+
207
+ logInfo('Checking prerequisites...');
208
+ checkPrerequisites();
209
+ logSuccess('Prerequisites OK');
210
+ log('');
211
+
212
+ // Clone the repository
213
+ logInfo(`Cloning Vibe Forge into ${FORGE_DIR}/...`);
214
+ try {
215
+ execSync(`git clone --depth 1 ${REPO_URL} ${FORGE_DIR}`, {
216
+ stdio: 'inherit',
217
+ cwd: process.cwd(),
218
+ });
219
+ logSuccess('Clone complete');
220
+ } catch {
221
+ logError('Failed to clone repository');
222
+ process.exit(1);
223
+ }
224
+
225
+ log('');
226
+
227
+ // Validate agents.json (alias collisions)
228
+ validateAgentsConfig(targetDir);
229
+
230
+ // Update project's .gitignore to ignore tool internals but keep project data
231
+ updateGitignore();
232
+
233
+ // Run the setup script
234
+ logInfo('Running setup...');
235
+ log('');
236
+
237
+ try {
238
+ const setupScript = path.join(targetDir, 'bin', 'forge-setup.sh');
239
+ await runBashScript(setupScript);
240
+ } catch (err) {
241
+ logError(`Setup failed: ${err.message}`);
242
+ process.exit(1);
243
+ }
244
+ }
245
+
246
+ function validateAgentsConfig(forgeDir) {
247
+ const checkScript = path.join(forgeDir, 'bin', 'lib', 'check-aliases.js');
248
+ if (!fs.existsSync(checkScript)) return;
249
+
250
+ try {
251
+ const agentsFile = path.join(forgeDir, 'config', 'agents.json');
252
+ execSync(`node "${checkScript}" "${agentsFile}"`, { stdio: 'pipe' });
253
+ const config = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
254
+ const agentCount = Object.keys(config.agents || {}).length;
255
+ logSuccess(`Agent config valid (${agentCount} agents, no alias collisions)`);
256
+ } catch (err) {
257
+ logError('Agent alias collisions detected in config/agents.json');
258
+ logInfo(err.stderr ? err.stderr.toString().trim() : 'Run node src/lib/check-aliases.js for details');
259
+ logError('Fix collisions before using Vibe Forge.');
260
+ process.exit(1);
261
+ }
262
+ }
263
+
264
+ function updateGitignore() {
265
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
266
+ const forgeIgnoreMarker = '# Vibe Forge';
267
+ const forgeIgnoreBlock = `
268
+ # Vibe Forge
269
+ # Tool internals (regenerated on update)
270
+ _vibe-forge/.git/
271
+ _vibe-forge/bin/
272
+ _vibe-forge/agents/
273
+ _vibe-forge/config/
274
+ _vibe-forge/docs/
275
+ _vibe-forge/tests/
276
+ _vibe-forge/src/
277
+ _vibe-forge/node_modules/
278
+ _vibe-forge/package*.json
279
+ _vibe-forge/*.md
280
+ _vibe-forge/LICENSE
281
+ _vibe-forge/.github/
282
+
283
+ # Keep project data (tasks, context) - these ARE committed
284
+ # !_vibe-forge/tasks/
285
+ # !_vibe-forge/context/
286
+ # !_vibe-forge/.claude/
287
+ `;
288
+
289
+ try {
290
+ let content = '';
291
+ if (fs.existsSync(gitignorePath)) {
292
+ content = fs.readFileSync(gitignorePath, 'utf8');
293
+ // Check if already has forge entries
294
+ if (content.includes(forgeIgnoreMarker)) {
295
+ return; // Already configured
296
+ }
297
+ }
298
+
299
+ // Append forge ignore block
300
+ const newContent = content.trimEnd() + '\n' + forgeIgnoreBlock;
301
+ fs.writeFileSync(gitignorePath, newContent);
302
+ logSuccess('Updated .gitignore for Vibe Forge');
303
+ } catch (err) {
304
+ logError(`Warning: Could not update .gitignore: ${err.message}`);
305
+ }
306
+ }
307
+
308
+ async function updateCommand() {
309
+ showBanner();
310
+
311
+ const targetDir = path.join(process.cwd(), FORGE_DIR);
312
+
313
+ // Check if initialized
314
+ if (!fs.existsSync(targetDir)) {
315
+ logError('Vibe Forge is not initialized in this project.');
316
+ log('');
317
+ log('Run: npx vibe-forge init');
318
+ process.exit(1);
319
+ }
320
+
321
+ logInfo('Updating Vibe Forge...');
322
+
323
+ try {
324
+ // Fetch and reset to origin/main - works even without tracking branch
325
+ execSync('git fetch origin', {
326
+ stdio: 'inherit',
327
+ cwd: targetDir,
328
+ });
329
+ execSync('git reset --hard origin/main', {
330
+ stdio: 'inherit',
331
+ cwd: targetDir,
332
+ });
333
+ logSuccess('Update complete');
334
+ } catch {
335
+ logError('Failed to update. Check your network connection or try deleting _vibe-forge and running init again.');
336
+ process.exit(1);
337
+ }
338
+ }
339
+
340
+ // Main entry point
341
+ async function main() {
342
+ const args = process.argv.slice(2);
343
+ const command = args[0] || 'help';
344
+
345
+ switch (command) {
346
+ case 'init':
347
+ await initCommand();
348
+ break;
349
+ case 'update':
350
+ await updateCommand();
351
+ break;
352
+ case 'agents':
353
+ showAgents();
354
+ break;
355
+ case 'version':
356
+ case '--version':
357
+ case '-v':
358
+ showVersion();
359
+ break;
360
+ case 'help':
361
+ case '--help':
362
+ case '-h':
363
+ default:
364
+ showHelp();
365
+ break;
366
+ }
367
+ }
368
+
369
+ main().catch((err) => {
370
+ logError(err.message);
371
+ process.exit(1);
372
+ });