vibe-forge 0.3.12 → 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 (85) 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 -0
  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 -0
  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 -230
  13. package/agents/aegis/personality.md +303 -269
  14. package/agents/anvil/personality.md +278 -211
  15. package/agents/architect/personality.md +260 -0
  16. package/agents/crucible/personality.md +362 -285
  17. package/agents/crucible-x/personality.md +210 -0
  18. package/agents/ember/personality.md +293 -245
  19. package/agents/flux/personality.md +248 -0
  20. package/agents/furnace/personality.md +342 -262
  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 -231
  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 -775
  38. package/bin/forge-setup.sh +661 -532
  39. package/bin/forge-spawn.sh +164 -159
  40. package/bin/forge.cmd +83 -83
  41. package/bin/forge.sh +566 -393
  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 -271
  46. package/bin/lib/constants.sh +241 -171
  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 -224
  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 -0
  59. package/bin/lib/terminal.js +452 -0
  60. package/bin/lib/util.sh +126 -0
  61. package/bin/lib/vcs.js +349 -0
  62. package/config/agent-manifest.yaml +237 -230
  63. package/config/agents.json +207 -85
  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 -0
  72. package/docs/architecture.md +194 -0
  73. package/docs/commands.md +451 -0
  74. package/docs/security.md +195 -144
  75. package/package.json +77 -48
  76. package/.claude/hooks/worker-loop.sh +0 -141
  77. package/.claude/settings.local.json +0 -29
  78. package/agents/forge-master/capabilities.md +0 -144
  79. package/agents/forge-master/context-template.md +0 -128
  80. package/agents/forge-master/personality.md +0 -138
  81. package/agents/sentinel/personality.md +0 -194
  82. package/context/forge-state.yaml +0 -19
  83. package/docs/TODO.md +0 -176
  84. package/docs/npm-publishing.md +0 -95
  85. package/tasks/review/task-001.md +0 -78
package/bin/forge.sh CHANGED
@@ -1,393 +1,566 @@
1
- #!/usr/bin/env bash
2
- #
3
- # Vibe Forge - Main Launcher
4
- # Starts the Planning Hub or specific worker agents
5
- #
6
- # Usage:
7
- # forge - Start Planning Hub (main session)
8
- # forge init - Initialize Vibe Forge
9
- # forge start <agent> - Start a specific worker agent
10
- # forge spawn <agent> - Spawn agent in new terminal window/tab
11
- # forge status - Show forge status
12
- # forge test - Validate setup
13
- # forge daemon - Start/stop the background daemon
14
- # forge help - Show help
15
- #
16
- # Security Note:
17
- # This script uses --dangerously-skip-permissions for Claude Code to enable
18
- # seamless agent startup. This is intentional for the forge workflow.
19
- # See docs/security.md for details.
20
- #
21
-
22
- set -e
23
-
24
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
25
- FORGE_ROOT="$(dirname "$SCRIPT_DIR")"
26
- CONFIG_FILE="$FORGE_ROOT/.forge/config.json"
27
-
28
- # =============================================================================
29
- # Load Shared Libraries
30
- # =============================================================================
31
-
32
- # shellcheck source=lib/colors.sh
33
- source "$SCRIPT_DIR/lib/colors.sh"
34
- # shellcheck source=lib/constants.sh
35
- source "$SCRIPT_DIR/lib/constants.sh"
36
- # shellcheck source=lib/config.sh
37
- source "$SCRIPT_DIR/lib/config.sh"
38
- # shellcheck source=lib/agents.sh
39
- source "$SCRIPT_DIR/lib/agents.sh"
40
-
41
- # Load agent configuration from JSON if available
42
- # This overwrites the fallback values in constants.sh
43
- if [[ -f "$FORGE_ROOT/$AGENTS_CONFIG" ]]; then
44
- if ! load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null; then
45
- log_warn "Failed to load agents.json, using fallback defaults"
46
- fi
47
- fi
48
-
49
- # =============================================================================
50
- # Commands
51
- # =============================================================================
52
-
53
- cmd_init() {
54
- "$SCRIPT_DIR/forge-setup.sh" "$@"
55
- }
56
-
57
- cmd_start() {
58
- local agent="${1:-hub}"
59
-
60
- # Load and validate config
61
- require_forge_config "$FORGE_ROOT"
62
-
63
- # Validate and resolve agent name (SECURITY: whitelist validation)
64
- local resolved
65
- resolved=$(resolve_agent "$agent") || {
66
- log_error "Unknown agent: $agent"
67
- echo ""
68
- show_available_agents
69
- exit 1
70
- }
71
-
72
- # Get personality path (SECURITY: path traversal protection)
73
- local personality_path
74
- personality_path=$(get_agent_personality_path "$FORGE_ROOT" "$resolved") || {
75
- log_error "Personality file not found for agent: $resolved"
76
- exit 1
77
- }
78
-
79
- # Get display name
80
- local agent_name
81
- agent_name=$(get_agent_display_name "$resolved")
82
-
83
- log_header "🔥 Starting $agent_name..."
84
- echo ""
85
-
86
- # Build the system prompt from personality file
87
- local system_prompt
88
- system_prompt=$(cat "$personality_path")
89
-
90
- # Add project context if it exists
91
- local project_context="$FORGE_ROOT/$CONTEXT_DIR/project-context.md"
92
- if [[ -f "$project_context" ]]; then
93
- system_prompt="$system_prompt
94
-
95
- ---
96
-
97
- # Project Context
98
-
99
- $(cat "$project_context")"
100
- fi
101
-
102
- # Add startup instructions based on agent type
103
- if [[ "$resolved" == "hub" ]]; then
104
- # Planning Hub startup - Party Mode Team
105
- system_prompt="$system_prompt
106
-
107
- ---
108
-
109
- # Startup Instructions
110
-
111
- On startup, you MUST immediately display the team assembly welcome as shown in the Startup Behavior section of your personality. Show the forge council members assembling with their icons and roles. Then check for any current work status.
112
- "
113
- else
114
- # Worker agent startup
115
- system_prompt="$system_prompt
116
-
117
- ---
118
-
119
- # Startup Instructions
120
-
121
- On startup:
122
- 1. Announce yourself briefly (icon, name, role)
123
- 2. Check tasks/pending/ and tasks/needs-changes/ for tasks assigned to you
124
- 3. If you find assigned tasks, IMMEDIATELY begin working on them (no confirmation needed)
125
- 4. If no tasks found, announce you're idle and ready for work
126
-
127
- You are autonomous - when assigned work exists, start it without asking permission.
128
- "
129
- fi
130
-
131
- # Launch Claude Code with the personality
132
- # --dangerously-skip-permissions avoids repeated prompts when starting agents
133
- # This is documented in docs/security.md
134
- if [[ "$resolved" == "hub" ]]; then
135
- claude --dangerously-skip-permissions --system-prompt "$system_prompt" "begin"
136
- else
137
- claude --dangerously-skip-permissions --system-prompt "$system_prompt" "startup"
138
- fi
139
- }
140
-
141
- cmd_status() {
142
- require_forge_config "$FORGE_ROOT"
143
-
144
- local state_file="$FORGE_ROOT/$CONTEXT_DIR/forge-state.yaml"
145
-
146
- echo ""
147
- log_header "🔥 Forge Status"
148
-
149
- if [[ -f "$state_file" ]]; then
150
- cat "$state_file"
151
- else
152
- echo "No active forge state."
153
- echo ""
154
- echo "Start the forge with: forge"
155
- fi
156
-
157
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
158
- echo ""
159
- }
160
-
161
- cmd_test() {
162
- require_forge_config "$FORGE_ROOT"
163
-
164
- echo ""
165
- log_header "🧪 Testing Vibe Forge setup..."
166
- echo ""
167
-
168
- # Test Claude Code
169
- echo "Testing Claude Code..."
170
- if claude --version &> /dev/null; then
171
- log_success "Claude Code working"
172
- else
173
- log_error "Claude Code not working"
174
- exit 1
175
- fi
176
-
177
- # Test personality loading
178
- echo ""
179
- echo "Testing personality loading..."
180
- local test_output
181
- test_output=$(claude --system-prompt "You are a test. Respond with only: FORGE_TEST_OK" --print "test" 2>&1 | head -1)
182
-
183
- if [[ "$test_output" == *"FORGE_TEST_OK"* || "$test_output" == *"test"* ]]; then
184
- log_success "Personality loading working"
185
- else
186
- log_warn "Personality loading may have issues"
187
- echo " Output: $test_output"
188
- fi
189
-
190
- echo ""
191
- log_success "🔥 Setup validated!"
192
- }
193
-
194
- cmd_spawn() {
195
- local agent="${1:-}"
196
-
197
- if [[ -z "$agent" ]]; then
198
- log_error "No agent specified."
199
- echo "Usage: forge spawn <agent>"
200
- echo ""
201
- show_available_agents
202
- exit 1
203
- fi
204
-
205
- # Validate agent before passing to spawn script (SECURITY: whitelist check)
206
- if ! is_valid_agent "$agent"; then
207
- log_error "Unknown agent: $agent"
208
- echo ""
209
- show_available_agents
210
- exit 1
211
- fi
212
-
213
- "$SCRIPT_DIR/forge-spawn.sh" "$agent"
214
- }
215
-
216
- cmd_daemon() {
217
- local action="${1:-status}"
218
-
219
- case "$action" in
220
- "start")
221
- echo "Starting forge daemon..."
222
- "$SCRIPT_DIR/forge-daemon.sh" start
223
- ;;
224
- "stop")
225
- echo "Stopping forge daemon..."
226
- "$SCRIPT_DIR/forge-daemon.sh" stop
227
- ;;
228
- "status")
229
- "$SCRIPT_DIR/forge-daemon.sh" status
230
- ;;
231
- "notifications"|"notify")
232
- shift
233
- "$SCRIPT_DIR/forge-daemon.sh" notifications "$@"
234
- ;;
235
- "clear")
236
- "$SCRIPT_DIR/forge-daemon.sh" clear
237
- ;;
238
- *)
239
- echo "Usage: forge daemon [start|stop|status|notifications|clear]"
240
- ;;
241
- esac
242
- }
243
-
244
- cmd_config() {
245
- local setting="${1:-}"
246
- local value="${2:-}"
247
- local config_file="$FORGE_ROOT/.forge/config.json"
248
-
249
- if [[ ! -f "$config_file" ]]; then
250
- log_error "Forge not initialized. Run 'forge init' first."
251
- exit 1
252
- fi
253
-
254
- case "$setting" in
255
- "worker-loop"|"loop")
256
- case "$value" in
257
- "on"|"true"|"1"|"enable"|"enabled")
258
- # Enable worker loop
259
- if jq -e '.worker_loop_enabled' "$config_file" > /dev/null 2>&1; then
260
- jq '.worker_loop_enabled = true' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
261
- else
262
- jq '. + {worker_loop_enabled: true}' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
263
- fi
264
- log_success "Worker Loop enabled"
265
- echo "Workers will now keep running to check for new tasks."
266
- ;;
267
- "off"|"false"|"0"|"disable"|"disabled")
268
- # Disable worker loop
269
- if jq -e '.worker_loop_enabled' "$config_file" > /dev/null 2>&1; then
270
- jq '.worker_loop_enabled = false' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
271
- else
272
- jq '. + {worker_loop_enabled: false}' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
273
- fi
274
- log_success "Worker Loop disabled"
275
- echo "Workers will exit after completing their tasks."
276
- ;;
277
- ""|"status")
278
- # Show current status
279
- local current
280
- current=$(jq -r '.worker_loop_enabled // false' "$config_file")
281
- if [[ "$current" == "true" ]]; then
282
- echo "Worker Loop: enabled"
283
- else
284
- echo "Worker Loop: disabled"
285
- fi
286
- ;;
287
- *)
288
- echo "Usage: forge config worker-loop [on|off|status]"
289
- ;;
290
- esac
291
- ;;
292
- "")
293
- # Show all config
294
- echo ""
295
- log_header "Forge Configuration"
296
- echo ""
297
- jq '.' "$config_file"
298
- echo ""
299
- ;;
300
- *)
301
- log_error "Unknown setting: $setting"
302
- echo ""
303
- echo "Available settings:"
304
- echo " worker-loop Toggle persistent worker mode (on/off)"
305
- echo ""
306
- echo "Usage: forge config <setting> [value]"
307
- ;;
308
- esac
309
- }
310
-
311
- cmd_help() {
312
- echo ""
313
- log_header "🔥 Vibe Forge"
314
- echo ""
315
- echo "Usage: forge [command] [options]"
316
- echo ""
317
- echo "Commands:"
318
- echo " (none) Start the Planning Hub (main session)"
319
- echo " init Initialize Vibe Forge for this project"
320
- echo " start <agent> Start a specific worker agent (in current terminal)"
321
- echo " spawn <agent> Spawn agent in new terminal window/tab"
322
- echo " status Show current forge status"
323
- echo " test Validate setup is working"
324
- echo " daemon <action> Manage background daemon (start|stop|status|notifications|clear)"
325
- echo " config <setting> View or change configuration settings"
326
- echo " help Show this help message"
327
- echo ""
328
- show_available_agents
329
- echo ""
330
- echo "Configuration:"
331
- echo " forge config Show all settings"
332
- echo " forge config worker-loop on Enable persistent worker mode"
333
- echo " forge config worker-loop off Disable persistent worker mode"
334
- echo ""
335
- echo "Examples:"
336
- echo " forge Start Planning Hub"
337
- echo " forge init Initialize for new project"
338
- echo " forge start anvil Start Anvil (frontend) agent"
339
- echo " forge spawn fe Spawn frontend agent in new terminal"
340
- echo " forge status Check current status"
341
- echo ""
342
- }
343
-
344
- # =============================================================================
345
- # Main
346
- # =============================================================================
347
-
348
- main() {
349
- local command="${1:-}"
350
-
351
- case "$command" in
352
- "init")
353
- shift
354
- cmd_init "$@"
355
- ;;
356
- "start")
357
- shift
358
- cmd_start "$@"
359
- ;;
360
- "spawn")
361
- shift
362
- cmd_spawn "$@"
363
- ;;
364
- "status")
365
- cmd_status
366
- ;;
367
- "test")
368
- cmd_test
369
- ;;
370
- "daemon")
371
- shift
372
- cmd_daemon "$@"
373
- ;;
374
- "config")
375
- shift
376
- cmd_config "$@"
377
- ;;
378
- "help"|"--help"|"-h")
379
- cmd_help
380
- ;;
381
- "")
382
- # Default: start Planning Hub
383
- cmd_start "hub"
384
- ;;
385
- *)
386
- log_error "Unknown command: $command"
387
- echo "Run 'forge help' for usage."
388
- exit 1
389
- ;;
390
- esac
391
- }
392
-
393
- main "$@"
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Vibe Forge - Main Launcher
4
+ # Starts the Planning Hub or specific worker agents
5
+ #
6
+ # Usage:
7
+ # forge - Start Planning Hub (main session)
8
+ # forge init - Initialize Vibe Forge
9
+ # forge start <agent> - Start a specific worker agent
10
+ # forge spawn <agent> - Spawn agent in new terminal window/tab
11
+ # forge status - Show forge status
12
+ # forge doctor - Full environment diagnostics (alias: test)
13
+ # forge daemon - Start/stop the background daemon
14
+ # forge help - Show help
15
+ #
16
+ # Security Note:
17
+ # Agents use allowlist-based permissions (.claude/settings.json) plus
18
+ # Heimdall pre-tool hooks instead of --dangerously-skip-permissions.
19
+ # See docs/security.md for the trust model.
20
+ #
21
+
22
+ set -e
23
+
24
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
25
+ FORGE_ROOT="$(dirname "$SCRIPT_DIR")"
26
+ CONFIG_FILE="$FORGE_ROOT/.forge/config.json"
27
+
28
+ # =============================================================================
29
+ # Load Shared Libraries
30
+ # =============================================================================
31
+
32
+ # shellcheck source=lib/colors.sh
33
+ source "$SCRIPT_DIR/lib/colors.sh"
34
+ # shellcheck source=lib/constants.sh
35
+ source "$SCRIPT_DIR/lib/constants.sh"
36
+ # shellcheck source=lib/config.sh
37
+ source "$SCRIPT_DIR/lib/config.sh"
38
+ # shellcheck source=lib/json.sh
39
+ source "$SCRIPT_DIR/lib/json.sh"
40
+ # shellcheck source=lib/agents.sh
41
+ source "$SCRIPT_DIR/lib/agents.sh"
42
+
43
+ # Load agent configuration from JSON if available
44
+ # This overwrites the fallback values in constants.sh
45
+ if [[ -f "$FORGE_ROOT/$AGENTS_CONFIG" ]]; then
46
+ if ! load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null; then
47
+ log_warn "Failed to load agents.json, using fallback defaults"
48
+ fi
49
+ fi
50
+
51
+ # =============================================================================
52
+ # Commands
53
+ # =============================================================================
54
+
55
+ cmd_init() {
56
+ "$SCRIPT_DIR/forge-setup.sh" "$@"
57
+ }
58
+
59
+ cmd_start() {
60
+ local agent="${1:-hub}"
61
+
62
+ # Load and validate config
63
+ require_forge_config "$FORGE_ROOT"
64
+
65
+ # Validate and resolve agent name (SECURITY: whitelist validation)
66
+ local resolved
67
+ resolved=$(resolve_agent "$agent") || {
68
+ log_error "Unknown agent: $agent"
69
+ echo ""
70
+ show_available_agents
71
+ exit $EXIT_INVALID_ARGUMENT
72
+ }
73
+
74
+ # Get personality path (SECURITY: path traversal protection)
75
+ local personality_path
76
+ personality_path=$(get_agent_personality_path "$FORGE_ROOT" "$resolved") || {
77
+ log_error "Personality file not found for agent: $resolved"
78
+ exit $EXIT_CONFIG_ERROR
79
+ }
80
+
81
+ # Get display name
82
+ local agent_name
83
+ agent_name=$(get_agent_display_name "$resolved")
84
+
85
+ log_header "🔥 Starting $agent_name..."
86
+ echo ""
87
+
88
+ # Build the system prompt from personality file
89
+ local system_prompt
90
+ system_prompt=$(cat "$personality_path")
91
+
92
+ # Add project context if it exists
93
+ local project_context="$FORGE_ROOT/$CONTEXT_DIR/project-context.md"
94
+ if [[ -f "$project_context" ]]; then
95
+ system_prompt="$system_prompt
96
+
97
+ ---
98
+
99
+ # Project Context
100
+
101
+ $(cat "$project_context")"
102
+ fi
103
+
104
+ # Add per-project agent overrides if they exist (T2-E3)
105
+ local agent_override="$FORGE_ROOT/$CONTEXT_DIR/agent-overrides/${resolved}.md"
106
+ if [[ -f "$agent_override" ]]; then
107
+ system_prompt="$system_prompt
108
+
109
+ ---
110
+
111
+ # Project-Specific Rules for $agent_name
112
+
113
+ $(cat "$agent_override")"
114
+ fi
115
+
116
+ # Check for handoff files from previous sessions (T2-F2)
117
+ local handoff_dir="$FORGE_ROOT/tasks/handoffs"
118
+ if [[ -d "$handoff_dir" ]]; then
119
+ for handoff in "$handoff_dir"/*-"${resolved}"-*.md "$handoff_dir"/*-handoff.md; do
120
+ if [[ -f "$handoff" ]]; then
121
+ local handoff_agent
122
+ handoff_agent=$(node "$SCRIPT_DIR/lib/frontmatter.js" "$handoff" "agent" 2>/dev/null | sed -n 's/^agent=//p')
123
+ if [[ "$handoff_agent" == "$resolved" ]]; then
124
+ system_prompt="$system_prompt
125
+
126
+ ---
127
+
128
+ # Session Handoff (from previous session)
129
+
130
+ $(cat "$handoff")"
131
+ break
132
+ fi
133
+ fi
134
+ done
135
+ fi
136
+
137
+ # Add startup instructions based on agent type
138
+ if [[ "$resolved" == "hub" ]]; then
139
+ # Planning Hub startup - Party Mode Team
140
+ system_prompt="$system_prompt
141
+
142
+ ---
143
+
144
+ # Startup Instructions
145
+
146
+ On startup, you MUST immediately display the team assembly welcome as shown in the Startup Behavior section of your personality. Show the forge council members assembling with their icons and roles. Then check for any current work status.
147
+ "
148
+ else
149
+ # Worker agent startup
150
+ system_prompt="$system_prompt
151
+
152
+ ---
153
+
154
+ # Startup Instructions
155
+
156
+ On startup:
157
+ 1. Announce yourself briefly (icon, name, role)
158
+ 2. Check tasks/pending/ and tasks/needs-changes/ for tasks assigned to you
159
+ 3. If you find assigned tasks, IMMEDIATELY begin working on them (no confirmation needed)
160
+ 4. If no tasks found, announce you're idle and ready for work
161
+
162
+ You are autonomous - when assigned work exists, start it without asking permission.
163
+ "
164
+ fi
165
+
166
+ # Launch Claude Code with the personality
167
+ # Agents use project-level .claude/settings.json for permissions (allowlist + Heimdall hooks)
168
+ # instead of --dangerously-skip-permissions. See docs/security.md.
169
+ if [[ "$resolved" == "hub" ]]; then
170
+ claude --system-prompt "$system_prompt" "begin"
171
+ else
172
+ claude --system-prompt "$system_prompt" "startup"
173
+ fi
174
+ }
175
+
176
+ cmd_status() {
177
+ require_forge_config "$FORGE_ROOT"
178
+
179
+ local state_file="$FORGE_ROOT/$CONTEXT_DIR/forge-state.yaml"
180
+
181
+ echo ""
182
+ log_header "🔥 Forge Status"
183
+
184
+ if [[ -f "$state_file" ]]; then
185
+ cat "$state_file"
186
+ else
187
+ echo "No active forge state."
188
+ echo ""
189
+ echo "Start the forge with: forge"
190
+ fi
191
+
192
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
193
+ echo ""
194
+ }
195
+
196
+ cmd_test() {
197
+ # Alias for doctor
198
+ cmd_doctor
199
+ }
200
+
201
+ cmd_doctor() {
202
+ echo ""
203
+ log_header "🩺 Forge Doctor"
204
+ echo ""
205
+
206
+ local issues=0
207
+
208
+ # --- Dependencies ---
209
+ echo "Dependencies"
210
+ echo "────────────────────────────────────────────────"
211
+
212
+ if claude --version &> /dev/null; then
213
+ local claude_ver
214
+ claude_ver=$(claude --version 2>&1 | head -1)
215
+ log_success "Claude Code: $claude_ver"
216
+ else
217
+ log_error "Claude Code: not found"
218
+ echo " Install from: https://claude.ai/download"
219
+ issues=$((issues + 1))
220
+ fi
221
+
222
+ if command -v node &> /dev/null; then
223
+ local node_ver
224
+ node_ver=$(node --version 2>&1)
225
+ log_success "Node.js: $node_ver"
226
+ # Warn if below Node 18
227
+ local node_major
228
+ node_major=$(node --version | sed 's/v\([0-9]*\).*/\1/')
229
+ if [[ "$node_major" -lt 18 ]]; then
230
+ log_warn "Node.js $node_ver is below recommended v18+"
231
+ fi
232
+ else
233
+ log_error "Node.js: not found"
234
+ issues=$((issues + 1))
235
+ fi
236
+
237
+ if command -v git &> /dev/null; then
238
+ local git_ver
239
+ git_ver=$(git --version 2>&1)
240
+ log_success "Git: $git_ver"
241
+ else
242
+ log_error "Git: not found"
243
+ issues=$((issues + 1))
244
+ fi
245
+
246
+ if command -v sqlite3 &> /dev/null; then
247
+ local sq_ver
248
+ sq_ver=$(sqlite3 --version 2>&1 | head -1)
249
+ log_success "SQLite3: $sq_ver"
250
+ else
251
+ log_warn "SQLite3: not found (daemon metrics disabled)"
252
+ fi
253
+
254
+ # --- Forge Config ---
255
+ echo ""
256
+ echo "Configuration"
257
+ echo "────────────────────────────────────────────────"
258
+
259
+ local config_file="$FORGE_ROOT/.forge/config.json"
260
+ local local_config_file="$FORGE_ROOT/.forge/config.local.json"
261
+
262
+ if [[ -f "$config_file" ]]; then
263
+ log_success "config.json: found"
264
+ local platform terminal
265
+ platform=$(json_get_string "$config_file" "platform" 2>/dev/null || echo "unknown")
266
+ terminal=$(json_get_string "$config_file" "terminal_type" 2>/dev/null || echo "unknown")
267
+ echo " Platform: $platform | Terminal: $terminal"
268
+ else
269
+ log_error "config.json: not found run 'forge init'"
270
+ issues=$((issues + 1))
271
+ fi
272
+
273
+ if [[ -f "$local_config_file" ]]; then
274
+ log_success "config.local.json: found (local overrides active)"
275
+ fi
276
+
277
+ # --- agents.json sync check ---
278
+ local agents_file="$FORGE_ROOT/config/agents.json"
279
+ if [[ -f "$agents_file" ]]; then
280
+ log_success "agents.json: found"
281
+ local agent_count
282
+ agent_count=$(node -e "const a=require('$agents_file'); console.log(Object.keys(a.agents||{}).length)" 2>/dev/null || echo "?")
283
+ echo " Agents defined: $agent_count"
284
+ else
285
+ log_error "agents.json: not found"
286
+ issues=$((issues + 1))
287
+ fi
288
+
289
+ # --- Alias collision check ---
290
+ if [[ -f "$agents_file" ]]; then
291
+ local collisions
292
+ collisions=$(node -e "
293
+ const a = require('$agents_file').agents || {};
294
+ const seen = {};
295
+ const dupes = [];
296
+ for (const [name, info] of Object.entries(a)) {
297
+ const aliases = [name, ...(info.aliases || [])];
298
+ for (const alias of aliases) {
299
+ if (seen[alias] && seen[alias] !== name) {
300
+ dupes.push(alias + ' (' + seen[alias] + ' vs ' + name + ')');
301
+ }
302
+ seen[alias] = name;
303
+ }
304
+ }
305
+ if (dupes.length) console.log('COLLISION: ' + dupes.join(', '));
306
+ " 2>/dev/null || true)
307
+ if [[ -n "$collisions" ]]; then
308
+ log_error "Alias collision detected: $collisions"
309
+ issues=$((issues + 1))
310
+ else
311
+ log_success "No alias collisions"
312
+ fi
313
+ fi
314
+
315
+ # --- Task directories ---
316
+ echo ""
317
+ echo "Task System"
318
+ echo "────────────────────────────────────────────────"
319
+
320
+ local tasks_dir="$FORGE_ROOT/tasks"
321
+ local required_dirs=("pending" "in-progress" "completed" "review" "approved" "merged" "needs-changes" "attention" "bugs")
322
+ for dir in "${required_dirs[@]}"; do
323
+ if [[ -d "$tasks_dir/$dir" ]]; then
324
+ local count
325
+ count=$(ls "$tasks_dir/$dir/"*.md 2>/dev/null | wc -l | tr -d ' ')
326
+ log_success "$dir/: $count task(s)"
327
+ else
328
+ log_warn "$dir/: missing (will be created by daemon)"
329
+ fi
330
+ done
331
+
332
+ # --- Daemon ---
333
+ echo ""
334
+ echo "Daemon"
335
+ echo "────────────────────────────────────────────────"
336
+
337
+ local pid_file="$FORGE_ROOT/.forge/daemon.pid"
338
+ if [[ -f "$pid_file" ]]; then
339
+ local pid
340
+ pid=$(cat "$pid_file" 2>/dev/null)
341
+ if kill -0 "$pid" 2>/dev/null; then
342
+ log_success "Daemon running (PID $pid)"
343
+ else
344
+ log_warn "Daemon PID file exists but process is dead — run 'forge daemon start'"
345
+ fi
346
+ else
347
+ log_warn "Daemon not running — run 'forge daemon start' for automated task routing"
348
+ fi
349
+
350
+ # --- Claude Code test ---
351
+ echo ""
352
+ echo "Claude Code Integration"
353
+ echo "────────────────────────────────────────────────"
354
+
355
+ local test_output
356
+ test_output=$(claude --system-prompt "You are a test. Reply only: FORGE_TEST_OK" --print "test" 2>&1 | head -1)
357
+ if [[ "$test_output" == *"FORGE_TEST_OK"* || "$test_output" == *"test"* ]]; then
358
+ log_success "Personality injection: working"
359
+ else
360
+ log_warn "Personality injection: response unexpected"
361
+ echo " Output: $test_output"
362
+ fi
363
+
364
+ # --- Summary ---
365
+ echo ""
366
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
367
+ if [[ $issues -eq 0 ]]; then
368
+ log_success "🔥 Forge looks healthy"
369
+ else
370
+ log_error "Found $issues issue(s) — resolve the errors above before starting"
371
+ fi
372
+ echo ""
373
+ }
374
+
375
+ cmd_spawn() {
376
+ local agent="${1:-}"
377
+
378
+ if [[ -z "$agent" ]]; then
379
+ log_error "No agent specified."
380
+ echo "Usage: forge spawn <agent>"
381
+ echo ""
382
+ show_available_agents
383
+ exit $EXIT_INVALID_ARGUMENT
384
+ fi
385
+
386
+ # Validate agent before passing to spawn script (SECURITY: whitelist check)
387
+ if ! is_valid_agent "$agent"; then
388
+ log_error "Unknown agent: $agent"
389
+ echo ""
390
+ show_available_agents
391
+ exit $EXIT_INVALID_ARGUMENT
392
+ fi
393
+
394
+ "$SCRIPT_DIR/forge-spawn.sh" "$agent"
395
+ }
396
+
397
+ cmd_daemon() {
398
+ local action="${1:-status}"
399
+
400
+ case "$action" in
401
+ "start")
402
+ echo "Starting forge daemon..."
403
+ "$SCRIPT_DIR/forge-daemon.sh" start
404
+ ;;
405
+ "stop")
406
+ echo "Stopping forge daemon..."
407
+ "$SCRIPT_DIR/forge-daemon.sh" stop
408
+ ;;
409
+ "status")
410
+ "$SCRIPT_DIR/forge-daemon.sh" status
411
+ ;;
412
+ "notifications"|"notify")
413
+ shift
414
+ "$SCRIPT_DIR/forge-daemon.sh" notifications "$@"
415
+ ;;
416
+ "clear")
417
+ "$SCRIPT_DIR/forge-daemon.sh" clear
418
+ ;;
419
+ *)
420
+ echo "Usage: forge daemon [start|stop|status|notifications|clear]"
421
+ ;;
422
+ esac
423
+ }
424
+
425
+ cmd_config() {
426
+ local setting="${1:-}"
427
+ local value="${2:-}"
428
+ local config_file="$FORGE_ROOT/.forge/config.json"
429
+
430
+ if [[ ! -f "$config_file" ]]; then
431
+ log_error "Forge not initialized. Run 'forge init' first."
432
+ exit $EXIT_CONFIG_ERROR
433
+ fi
434
+
435
+ case "$setting" in
436
+ "worker-loop"|"loop")
437
+ case "$value" in
438
+ "on"|"true"|"1"|"enable"|"enabled")
439
+ # Enable worker loop
440
+ json_write_bool "$config_file" "worker_loop_enabled" true
441
+ log_success "Worker Loop enabled"
442
+ echo "Workers will now keep running to check for new tasks."
443
+ ;;
444
+ "off"|"false"|"0"|"disable"|"disabled")
445
+ # Disable worker loop
446
+ json_write_bool "$config_file" "worker_loop_enabled" false
447
+ log_success "Worker Loop disabled"
448
+ echo "Workers will exit after completing their tasks."
449
+ ;;
450
+ ""|"status")
451
+ # Show current status
452
+ local current
453
+ current=$(json_read "$config_file" "worker_loop_enabled" "false")
454
+ if [[ "$current" == "true" ]]; then
455
+ echo "Worker Loop: enabled"
456
+ else
457
+ echo "Worker Loop: disabled"
458
+ fi
459
+ ;;
460
+ *)
461
+ echo "Usage: forge config worker-loop [on|off|status]"
462
+ ;;
463
+ esac
464
+ ;;
465
+ "")
466
+ # Show all config
467
+ echo ""
468
+ log_header "Forge Configuration"
469
+ echo ""
470
+ json_pretty "$config_file"
471
+ echo ""
472
+ ;;
473
+ *)
474
+ log_error "Unknown setting: $setting"
475
+ echo ""
476
+ echo "Available settings:"
477
+ echo " worker-loop Toggle persistent worker mode (on/off)"
478
+ echo ""
479
+ echo "Usage: forge config <setting> [value]"
480
+ ;;
481
+ esac
482
+ }
483
+
484
+ cmd_help() {
485
+ echo ""
486
+ log_header "🔥 Vibe Forge"
487
+ echo ""
488
+ echo "Usage: forge [command] [options]"
489
+ echo ""
490
+ echo "Commands:"
491
+ echo " (none) Start the Planning Hub (main session)"
492
+ echo " init Initialize Vibe Forge for this project"
493
+ echo " start <agent> Start a specific worker agent (in current terminal)"
494
+ echo " spawn <agent> Spawn agent in new terminal window/tab"
495
+ echo " status Show current forge status"
496
+ echo " doctor Full environment diagnostics (alias: test)"
497
+ echo " daemon <action> Manage background daemon (start|stop|status|notifications|clear)"
498
+ echo " config <setting> View or change configuration settings"
499
+ echo " help Show this help message"
500
+ echo ""
501
+ show_available_agents
502
+ echo ""
503
+ echo "Configuration:"
504
+ echo " forge config Show all settings"
505
+ echo " forge config worker-loop on Enable persistent worker mode"
506
+ echo " forge config worker-loop off Disable persistent worker mode"
507
+ echo ""
508
+ echo "Examples:"
509
+ echo " forge Start Planning Hub"
510
+ echo " forge init Initialize for new project"
511
+ echo " forge start anvil Start Anvil (frontend) agent"
512
+ echo " forge spawn fe Spawn frontend agent in new terminal"
513
+ echo " forge status Check current status"
514
+ echo ""
515
+ }
516
+
517
+ # =============================================================================
518
+ # Main
519
+ # =============================================================================
520
+
521
+ main() {
522
+ local command="${1:-}"
523
+
524
+ case "$command" in
525
+ "init")
526
+ shift
527
+ cmd_init "$@"
528
+ ;;
529
+ "start")
530
+ shift
531
+ cmd_start "$@"
532
+ ;;
533
+ "spawn")
534
+ shift
535
+ cmd_spawn "$@"
536
+ ;;
537
+ "status")
538
+ cmd_status
539
+ ;;
540
+ "test"|"doctor")
541
+ cmd_doctor
542
+ ;;
543
+ "daemon")
544
+ shift
545
+ cmd_daemon "$@"
546
+ ;;
547
+ "config")
548
+ shift
549
+ cmd_config "$@"
550
+ ;;
551
+ "help"|"--help"|"-h")
552
+ cmd_help
553
+ ;;
554
+ "")
555
+ # Default: start Planning Hub
556
+ cmd_start "hub"
557
+ ;;
558
+ *)
559
+ log_error "Unknown command: $command"
560
+ echo "Run 'forge help' for usage."
561
+ exit $EXIT_INVALID_ARGUMENT
562
+ ;;
563
+ esac
564
+ }
565
+
566
+ main "$@"