vibe-forge 0.4.0 → 0.8.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 (129) hide show
  1. package/.claude/commands/clear-attention.md +63 -63
  2. package/.claude/commands/compact-context.md +52 -0
  3. package/.claude/commands/configure-vcs.md +102 -102
  4. package/.claude/commands/forge.md +218 -171
  5. package/.claude/commands/need-help.md +77 -77
  6. package/.claude/commands/update-status.md +64 -64
  7. package/.claude/commands/worker-loop.md +106 -106
  8. package/.claude/hooks/worker-loop.js +217 -187
  9. package/.claude/scripts/setup-worker-loop.sh +45 -45
  10. package/.claude/settings.json +89 -0
  11. package/LICENSE +21 -21
  12. package/README.md +253 -232
  13. package/agents/aegis/personality.md +303 -269
  14. package/agents/anvil/personality.md +278 -240
  15. package/agents/architect/personality.md +260 -234
  16. package/agents/crucible/personality.md +362 -309
  17. package/agents/crucible-x/personality.md +210 -0
  18. package/agents/ember/personality.md +293 -265
  19. package/agents/flux/personality.md +248 -0
  20. package/agents/furnace/personality.md +342 -291
  21. package/agents/herald/personality.md +249 -247
  22. package/agents/loki/personality.md +108 -0
  23. package/agents/oracle/personality.md +284 -0
  24. package/agents/pixel/personality.md +140 -0
  25. package/agents/planning-hub/personality.md +473 -251
  26. package/agents/scribe/personality.md +253 -251
  27. package/agents/slag/personality.md +268 -0
  28. package/agents/temper/personality.md +270 -0
  29. package/bin/cli.js +372 -325
  30. package/bin/dashboard/api/agents.js +333 -0
  31. package/bin/dashboard/api/dispatch.js +507 -0
  32. package/bin/dashboard/api/tasks.js +416 -0
  33. package/bin/dashboard/public/assets/index-BpHfsx1r.js +2 -0
  34. package/bin/dashboard/public/assets/index-QODv4Zn9.css +1 -0
  35. package/bin/dashboard/public/index.html +14 -0
  36. package/bin/dashboard/server.js +645 -0
  37. package/bin/forge-daemon.sh +477 -851
  38. package/bin/forge-setup.sh +661 -645
  39. package/bin/forge-spawn.sh +164 -164
  40. package/bin/forge.cmd +83 -83
  41. package/bin/forge.sh +566 -387
  42. package/bin/lib/agents.sh +177 -177
  43. package/bin/lib/check-aliases.js +50 -0
  44. package/bin/lib/colors.sh +44 -44
  45. package/bin/lib/config.sh +347 -313
  46. package/bin/lib/constants.sh +241 -206
  47. package/bin/lib/daemon/budgets.sh +107 -0
  48. package/bin/lib/daemon/dependencies.sh +146 -0
  49. package/bin/lib/daemon/display.sh +128 -0
  50. package/bin/lib/daemon/notifications.sh +273 -0
  51. package/bin/lib/daemon/routing.sh +93 -0
  52. package/bin/lib/daemon/state.sh +163 -0
  53. package/bin/lib/daemon/sync.sh +103 -0
  54. package/bin/lib/database.sh +357 -305
  55. package/bin/lib/frontmatter.js +106 -0
  56. package/bin/lib/heimdall-setup.js +113 -0
  57. package/bin/lib/heimdall.js +265 -0
  58. package/bin/lib/json.sh +264 -258
  59. package/bin/lib/terminal.js +452 -446
  60. package/bin/lib/util.sh +126 -126
  61. package/bin/lib/vcs.js +349 -349
  62. package/config/agent-manifest.yaml +237 -243
  63. package/config/agents.json +207 -132
  64. package/config/task-template.md +159 -87
  65. package/config/task-types.yaml +111 -106
  66. package/config/templates/handoff-template.md +40 -0
  67. package/context/agent-overrides/README.md +41 -0
  68. package/context/architecture.md +42 -0
  69. package/context/modern-conventions.md +129 -129
  70. package/context/project-context-template.md +122 -122
  71. package/docs/agents.md +473 -409
  72. package/docs/architecture.md +194 -162
  73. package/docs/commands.md +451 -388
  74. package/docs/security.md +195 -144
  75. package/package.json +77 -50
  76. package/.claude/settings.local.json +0 -33
  77. package/agents/forge-master/capabilities.md +0 -144
  78. package/agents/forge-master/context-template.md +0 -128
  79. package/agents/forge-master/personality.md +0 -138
  80. package/agents/sentinel/personality.md +0 -194
  81. package/context/forge-state.yaml +0 -19
  82. package/docs/TODO.md +0 -150
  83. package/docs/getting-started.md +0 -243
  84. package/docs/npm-publishing.md +0 -95
  85. package/docs/workflows/README.md +0 -32
  86. package/docs/workflows/azure-devops.md +0 -108
  87. package/docs/workflows/bitbucket.md +0 -104
  88. package/docs/workflows/git-only.md +0 -130
  89. package/docs/workflows/gitea.md +0 -168
  90. package/docs/workflows/github.md +0 -103
  91. package/docs/workflows/gitlab.md +0 -105
  92. package/docs/workflows.md +0 -454
  93. package/tasks/completed/ARCH-001-duplicate-agent-config.md +0 -121
  94. package/tasks/completed/ARCH-002-mixed-bash-node-implementation.md +0 -88
  95. package/tasks/completed/ARCH-003-worker-loop-hook-duplication.md +0 -77
  96. package/tasks/completed/ARCH-009-test-organization.md +0 -78
  97. package/tasks/completed/ARCH-011-jq-vs-nodejs-json.md +0 -94
  98. package/tasks/completed/ARCH-012-tmp-files-in-root.md +0 -71
  99. package/tasks/completed/ARCH-013-exit-code-constants.md +0 -65
  100. package/tasks/completed/ARCH-014-sed-incompatibility.md +0 -96
  101. package/tasks/completed/ARCH-015-docs-todo-tracking.md +0 -83
  102. package/tasks/completed/CLEAN-001.md +0 -38
  103. package/tasks/completed/CLEAN-003.md +0 -47
  104. package/tasks/completed/CLEAN-004.md +0 -56
  105. package/tasks/completed/CLEAN-005.md +0 -75
  106. package/tasks/completed/CLEAN-006.md +0 -47
  107. package/tasks/completed/CLEAN-007.md +0 -34
  108. package/tasks/completed/CLEAN-008.md +0 -49
  109. package/tasks/completed/CLEAN-012.md +0 -58
  110. package/tasks/completed/CLEAN-013.md +0 -45
  111. package/tasks/completed/SEC-001-sql-injection-fix.md +0 -58
  112. package/tasks/completed/SEC-002-notification-injection-fix.md +0 -45
  113. package/tasks/completed/SEC-003-eval-injection-fix.md +0 -54
  114. package/tasks/completed/SEC-004-pid-race-condition-fix.md +0 -49
  115. package/tasks/completed/SEC-005-worker-loop-path-fix.md +0 -51
  116. package/tasks/completed/SEC-006-eval-agent-names.md +0 -55
  117. package/tasks/completed/SEC-007-spawn-escaping.md +0 -67
  118. package/tasks/pending/ARCH-004-git-bash-detection-duplication.md +0 -72
  119. package/tasks/pending/ARCH-005-missing-src-directory.md +0 -95
  120. package/tasks/pending/ARCH-006-task-template-location.md +0 -64
  121. package/tasks/pending/ARCH-007-daemon-monolith.md +0 -91
  122. package/tasks/pending/ARCH-008-forge-master-vs-hub.md +0 -81
  123. package/tasks/pending/ARCH-010-missing-index-files.md +0 -84
  124. package/tasks/pending/CLEAN-002.md +0 -29
  125. package/tasks/pending/CLEAN-009.md +0 -31
  126. package/tasks/pending/CLEAN-010.md +0 -30
  127. package/tasks/pending/CLEAN-011.md +0 -30
  128. package/tasks/pending/CLEAN-014.md +0 -32
  129. package/tasks/review/task-001.md +0 -78
package/bin/cli.js CHANGED
@@ -1,325 +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/SpasticPalate/vibe-forge.git';
21
- const FORGE_DIR = '_vibe-forge';
22
-
23
- // Colors for terminal output
24
- // NOTE: Intentionally self-contained - cli.js runs standalone via npx before
25
- // the rest of Vibe Forge is installed, so it cannot share with colors.sh
26
- const colors = {
27
- reset: '\x1b[0m',
28
- red: '\x1b[31m',
29
- green: '\x1b[32m',
30
- yellow: '\x1b[33m',
31
- blue: '\x1b[34m',
32
- cyan: '\x1b[36m',
33
- };
34
-
35
- function log(message, color = colors.reset) {
36
- console.log(`${color}${message}${colors.reset}`);
37
- }
38
-
39
- function logError(message) {
40
- log(`Error: ${message}`, colors.red);
41
- }
42
-
43
- function logSuccess(message) {
44
- log(message, colors.green);
45
- }
46
-
47
- function logInfo(message) {
48
- log(message, colors.blue);
49
- }
50
-
51
- function showBanner() {
52
- console.log(`
53
- ${colors.yellow}🔥 Vibe Forge${colors.reset}
54
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
55
- Multi-agent development orchestration
56
- `);
57
- }
58
-
59
- function showHelp() {
60
- showBanner();
61
- console.log(`Usage: npx vibe-forge <command>
62
-
63
- Commands:
64
- init Initialize Vibe Forge in the current project
65
- update Update Vibe Forge to the latest version
66
- version Show version information
67
- help Show this help message
68
-
69
- Examples:
70
- npx vibe-forge init # Set up Forge in your project
71
- npx vibe-forge update # Update to latest version
72
- `);
73
- }
74
-
75
- function showVersion() {
76
- console.log(`Vibe Forge v${VERSION}`);
77
- }
78
-
79
- function checkPrerequisites() {
80
- // Check for git
81
- try {
82
- execSync('git --version', { stdio: 'pipe' });
83
- } catch {
84
- logError('Git is not installed. Please install Git first.');
85
- logInfo(' Windows: winget install Git.Git');
86
- logInfo(' macOS: brew install git');
87
- logInfo(' Linux: sudo apt install git');
88
- process.exit(1);
89
- }
90
-
91
- // Check for Claude Code
92
- try {
93
- execSync('claude --version', { stdio: 'pipe' });
94
- } catch {
95
- logError('Claude Code CLI is not installed.');
96
- logInfo(' Install from: https://claude.ai/download');
97
- process.exit(1);
98
- }
99
- }
100
-
101
- function isWindows() {
102
- return os.platform() === 'win32';
103
- }
104
-
105
- function getBashPath() {
106
- if (!isWindows()) {
107
- return 'bash';
108
- }
109
-
110
- // Common Git Bash paths on Windows
111
- const paths = [
112
- 'C:\\Program Files\\Git\\bin\\bash.exe',
113
- 'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
114
- path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Git', 'bin', 'bash.exe'),
115
- ];
116
-
117
- for (const p of paths) {
118
- if (fs.existsSync(p)) {
119
- return p;
120
- }
121
- }
122
-
123
- // Try to find via where command
124
- try {
125
- const gitPath = execSync('where git', { stdio: 'pipe' }).toString().trim().split('\n')[0];
126
- const gitDir = path.dirname(path.dirname(gitPath));
127
- const bashPath = path.join(gitDir, 'bin', 'bash.exe');
128
- if (fs.existsSync(bashPath)) {
129
- return bashPath;
130
- }
131
- } catch {
132
- // Ignore
133
- }
134
-
135
- return null;
136
- }
137
-
138
- function runBashScript(scriptPath, args = []) {
139
- const bashPath = getBashPath();
140
-
141
- if (!bashPath) {
142
- logError('Could not find bash. Please install Git Bash on Windows.');
143
- process.exit(1);
144
- }
145
-
146
- return new Promise((resolve, reject) => {
147
- const child = spawn(bashPath, [scriptPath, ...args], {
148
- stdio: 'inherit',
149
- cwd: process.cwd(),
150
- env: {
151
- ...process.env,
152
- CLAUDE_CODE_GIT_BASH_PATH: bashPath,
153
- },
154
- });
155
-
156
- child.on('close', (code) => {
157
- if (code === 0) {
158
- resolve();
159
- } else {
160
- reject(new Error(`Script exited with code ${code}`));
161
- }
162
- });
163
-
164
- child.on('error', (err) => {
165
- reject(err);
166
- });
167
- });
168
- }
169
-
170
- async function initCommand() {
171
- showBanner();
172
-
173
- const targetDir = path.join(process.cwd(), FORGE_DIR);
174
-
175
- // Check if already initialized
176
- if (fs.existsSync(targetDir)) {
177
- logInfo(`Vibe Forge already exists at ${FORGE_DIR}/`);
178
- log('');
179
- log('To update, run: npx vibe-forge update');
180
- log('To reinitialize, delete the _vibe-forge folder first.');
181
- return;
182
- }
183
-
184
- logInfo('Checking prerequisites...');
185
- checkPrerequisites();
186
- logSuccess('Prerequisites OK');
187
- log('');
188
-
189
- // Clone the repository
190
- logInfo(`Cloning Vibe Forge into ${FORGE_DIR}/...`);
191
- try {
192
- execSync(`git clone --depth 1 ${REPO_URL} ${FORGE_DIR}`, {
193
- stdio: 'inherit',
194
- cwd: process.cwd(),
195
- });
196
- logSuccess('Clone complete');
197
- } catch {
198
- logError('Failed to clone repository');
199
- process.exit(1);
200
- }
201
-
202
- log('');
203
-
204
- // Update project's .gitignore to ignore tool internals but keep project data
205
- updateGitignore();
206
-
207
- // Run the setup script
208
- logInfo('Running setup...');
209
- log('');
210
-
211
- try {
212
- const setupScript = path.join(targetDir, 'bin', 'forge-setup.sh');
213
- await runBashScript(setupScript);
214
- } catch (err) {
215
- logError(`Setup failed: ${err.message}`);
216
- process.exit(1);
217
- }
218
- }
219
-
220
- function updateGitignore() {
221
- const gitignorePath = path.join(process.cwd(), '.gitignore');
222
- const forgeIgnoreMarker = '# Vibe Forge';
223
- const forgeIgnoreBlock = `
224
- # Vibe Forge
225
- # Tool internals (regenerated on update)
226
- _vibe-forge/.git/
227
- _vibe-forge/bin/
228
- _vibe-forge/agents/
229
- _vibe-forge/config/
230
- _vibe-forge/docs/
231
- _vibe-forge/tests/
232
- _vibe-forge/src/
233
- _vibe-forge/node_modules/
234
- _vibe-forge/package*.json
235
- _vibe-forge/*.md
236
- _vibe-forge/LICENSE
237
- _vibe-forge/.github/
238
-
239
- # Keep project data (tasks, context) - these ARE committed
240
- # !_vibe-forge/tasks/
241
- # !_vibe-forge/context/
242
- # !_vibe-forge/.claude/
243
- `;
244
-
245
- try {
246
- let content = '';
247
- if (fs.existsSync(gitignorePath)) {
248
- content = fs.readFileSync(gitignorePath, 'utf8');
249
- // Check if already has forge entries
250
- if (content.includes(forgeIgnoreMarker)) {
251
- return; // Already configured
252
- }
253
- }
254
-
255
- // Append forge ignore block
256
- const newContent = content.trimEnd() + '\n' + forgeIgnoreBlock;
257
- fs.writeFileSync(gitignorePath, newContent);
258
- logSuccess('Updated .gitignore for Vibe Forge');
259
- } catch (err) {
260
- logError(`Warning: Could not update .gitignore: ${err.message}`);
261
- }
262
- }
263
-
264
- async function updateCommand() {
265
- showBanner();
266
-
267
- const targetDir = path.join(process.cwd(), FORGE_DIR);
268
-
269
- // Check if initialized
270
- if (!fs.existsSync(targetDir)) {
271
- logError('Vibe Forge is not initialized in this project.');
272
- log('');
273
- log('Run: npx vibe-forge init');
274
- process.exit(1);
275
- }
276
-
277
- logInfo('Updating Vibe Forge...');
278
-
279
- try {
280
- // Fetch and reset to origin/main - works even without tracking branch
281
- execSync('git fetch origin', {
282
- stdio: 'inherit',
283
- cwd: targetDir,
284
- });
285
- execSync('git reset --hard origin/main', {
286
- stdio: 'inherit',
287
- cwd: targetDir,
288
- });
289
- logSuccess('Update complete');
290
- } catch {
291
- logError('Failed to update. Check your network connection or try deleting _vibe-forge and running init again.');
292
- process.exit(1);
293
- }
294
- }
295
-
296
- // Main entry point
297
- async function main() {
298
- const args = process.argv.slice(2);
299
- const command = args[0] || 'help';
300
-
301
- switch (command) {
302
- case 'init':
303
- await initCommand();
304
- break;
305
- case 'update':
306
- await updateCommand();
307
- break;
308
- case 'version':
309
- case '--version':
310
- case '-v':
311
- showVersion();
312
- break;
313
- case 'help':
314
- case '--help':
315
- case '-h':
316
- default:
317
- showHelp();
318
- break;
319
- }
320
- }
321
-
322
- main().catch((err) => {
323
- logError(err.message);
324
- process.exit(1);
325
- });
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
+ });