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/forge.sh CHANGED
@@ -1,387 +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/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 startup instructions based on agent type
105
- if [[ "$resolved" == "hub" ]]; then
106
- # Planning Hub startup - Party Mode Team
107
- system_prompt="$system_prompt
108
-
109
- ---
110
-
111
- # Startup Instructions
112
-
113
- 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.
114
- "
115
- else
116
- # Worker agent startup
117
- system_prompt="$system_prompt
118
-
119
- ---
120
-
121
- # Startup Instructions
122
-
123
- On startup:
124
- 1. Announce yourself briefly (icon, name, role)
125
- 2. Check tasks/pending/ and tasks/needs-changes/ for tasks assigned to you
126
- 3. If you find assigned tasks, IMMEDIATELY begin working on them (no confirmation needed)
127
- 4. If no tasks found, announce you're idle and ready for work
128
-
129
- You are autonomous - when assigned work exists, start it without asking permission.
130
- "
131
- fi
132
-
133
- # Launch Claude Code with the personality
134
- # --dangerously-skip-permissions avoids repeated prompts when starting agents
135
- # This is documented in docs/security.md
136
- if [[ "$resolved" == "hub" ]]; then
137
- claude --dangerously-skip-permissions --system-prompt "$system_prompt" "begin"
138
- else
139
- claude --dangerously-skip-permissions --system-prompt "$system_prompt" "startup"
140
- fi
141
- }
142
-
143
- cmd_status() {
144
- require_forge_config "$FORGE_ROOT"
145
-
146
- local state_file="$FORGE_ROOT/$CONTEXT_DIR/forge-state.yaml"
147
-
148
- echo ""
149
- log_header "🔥 Forge Status"
150
-
151
- if [[ -f "$state_file" ]]; then
152
- cat "$state_file"
153
- else
154
- echo "No active forge state."
155
- echo ""
156
- echo "Start the forge with: forge"
157
- fi
158
-
159
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
160
- echo ""
161
- }
162
-
163
- cmd_test() {
164
- require_forge_config "$FORGE_ROOT"
165
-
166
- echo ""
167
- log_header "🧪 Testing Vibe Forge setup..."
168
- echo ""
169
-
170
- # Test Claude Code
171
- echo "Testing Claude Code..."
172
- if claude --version &> /dev/null; then
173
- log_success "Claude Code working"
174
- else
175
- log_error "Claude Code not working"
176
- exit $EXIT_DEPENDENCY_MISSING
177
- fi
178
-
179
- # Test personality loading
180
- echo ""
181
- echo "Testing personality loading..."
182
- local test_output
183
- test_output=$(claude --system-prompt "You are a test. Respond with only: FORGE_TEST_OK" --print "test" 2>&1 | head -1)
184
-
185
- if [[ "$test_output" == *"FORGE_TEST_OK"* || "$test_output" == *"test"* ]]; then
186
- log_success "Personality loading working"
187
- else
188
- log_warn "Personality loading may have issues"
189
- echo " Output: $test_output"
190
- fi
191
-
192
- echo ""
193
- log_success "🔥 Setup validated!"
194
- }
195
-
196
- cmd_spawn() {
197
- local agent="${1:-}"
198
-
199
- if [[ -z "$agent" ]]; then
200
- log_error "No agent specified."
201
- echo "Usage: forge spawn <agent>"
202
- echo ""
203
- show_available_agents
204
- exit $EXIT_INVALID_ARGUMENT
205
- fi
206
-
207
- # Validate agent before passing to spawn script (SECURITY: whitelist check)
208
- if ! is_valid_agent "$agent"; then
209
- log_error "Unknown agent: $agent"
210
- echo ""
211
- show_available_agents
212
- exit $EXIT_INVALID_ARGUMENT
213
- fi
214
-
215
- "$SCRIPT_DIR/forge-spawn.sh" "$agent"
216
- }
217
-
218
- cmd_daemon() {
219
- local action="${1:-status}"
220
-
221
- case "$action" in
222
- "start")
223
- echo "Starting forge daemon..."
224
- "$SCRIPT_DIR/forge-daemon.sh" start
225
- ;;
226
- "stop")
227
- echo "Stopping forge daemon..."
228
- "$SCRIPT_DIR/forge-daemon.sh" stop
229
- ;;
230
- "status")
231
- "$SCRIPT_DIR/forge-daemon.sh" status
232
- ;;
233
- "notifications"|"notify")
234
- shift
235
- "$SCRIPT_DIR/forge-daemon.sh" notifications "$@"
236
- ;;
237
- "clear")
238
- "$SCRIPT_DIR/forge-daemon.sh" clear
239
- ;;
240
- *)
241
- echo "Usage: forge daemon [start|stop|status|notifications|clear]"
242
- ;;
243
- esac
244
- }
245
-
246
- cmd_config() {
247
- local setting="${1:-}"
248
- local value="${2:-}"
249
- local config_file="$FORGE_ROOT/.forge/config.json"
250
-
251
- if [[ ! -f "$config_file" ]]; then
252
- log_error "Forge not initialized. Run 'forge init' first."
253
- exit $EXIT_CONFIG_ERROR
254
- fi
255
-
256
- case "$setting" in
257
- "worker-loop"|"loop")
258
- case "$value" in
259
- "on"|"true"|"1"|"enable"|"enabled")
260
- # Enable worker loop
261
- json_write_bool "$config_file" "worker_loop_enabled" true
262
- log_success "Worker Loop enabled"
263
- echo "Workers will now keep running to check for new tasks."
264
- ;;
265
- "off"|"false"|"0"|"disable"|"disabled")
266
- # Disable worker loop
267
- json_write_bool "$config_file" "worker_loop_enabled" false
268
- log_success "Worker Loop disabled"
269
- echo "Workers will exit after completing their tasks."
270
- ;;
271
- ""|"status")
272
- # Show current status
273
- local current
274
- current=$(json_read "$config_file" "worker_loop_enabled" "false")
275
- if [[ "$current" == "true" ]]; then
276
- echo "Worker Loop: enabled"
277
- else
278
- echo "Worker Loop: disabled"
279
- fi
280
- ;;
281
- *)
282
- echo "Usage: forge config worker-loop [on|off|status]"
283
- ;;
284
- esac
285
- ;;
286
- "")
287
- # Show all config
288
- echo ""
289
- log_header "Forge Configuration"
290
- echo ""
291
- json_pretty "$config_file"
292
- echo ""
293
- ;;
294
- *)
295
- log_error "Unknown setting: $setting"
296
- echo ""
297
- echo "Available settings:"
298
- echo " worker-loop Toggle persistent worker mode (on/off)"
299
- echo ""
300
- echo "Usage: forge config <setting> [value]"
301
- ;;
302
- esac
303
- }
304
-
305
- cmd_help() {
306
- echo ""
307
- log_header "🔥 Vibe Forge"
308
- echo ""
309
- echo "Usage: forge [command] [options]"
310
- echo ""
311
- echo "Commands:"
312
- echo " (none) Start the Planning Hub (main session)"
313
- echo " init Initialize Vibe Forge for this project"
314
- echo " start <agent> Start a specific worker agent (in current terminal)"
315
- echo " spawn <agent> Spawn agent in new terminal window/tab"
316
- echo " status Show current forge status"
317
- echo " test Validate setup is working"
318
- echo " daemon <action> Manage background daemon (start|stop|status|notifications|clear)"
319
- echo " config <setting> View or change configuration settings"
320
- echo " help Show this help message"
321
- echo ""
322
- show_available_agents
323
- echo ""
324
- echo "Configuration:"
325
- echo " forge config Show all settings"
326
- echo " forge config worker-loop on Enable persistent worker mode"
327
- echo " forge config worker-loop off Disable persistent worker mode"
328
- echo ""
329
- echo "Examples:"
330
- echo " forge Start Planning Hub"
331
- echo " forge init Initialize for new project"
332
- echo " forge start anvil Start Anvil (frontend) agent"
333
- echo " forge spawn fe Spawn frontend agent in new terminal"
334
- echo " forge status Check current status"
335
- echo ""
336
- }
337
-
338
- # =============================================================================
339
- # Main
340
- # =============================================================================
341
-
342
- main() {
343
- local command="${1:-}"
344
-
345
- case "$command" in
346
- "init")
347
- shift
348
- cmd_init "$@"
349
- ;;
350
- "start")
351
- shift
352
- cmd_start "$@"
353
- ;;
354
- "spawn")
355
- shift
356
- cmd_spawn "$@"
357
- ;;
358
- "status")
359
- cmd_status
360
- ;;
361
- "test")
362
- cmd_test
363
- ;;
364
- "daemon")
365
- shift
366
- cmd_daemon "$@"
367
- ;;
368
- "config")
369
- shift
370
- cmd_config "$@"
371
- ;;
372
- "help"|"--help"|"-h")
373
- cmd_help
374
- ;;
375
- "")
376
- # Default: start Planning Hub
377
- cmd_start "hub"
378
- ;;
379
- *)
380
- log_error "Unknown command: $command"
381
- echo "Run 'forge help' for usage."
382
- exit $EXIT_INVALID_ARGUMENT
383
- ;;
384
- esac
385
- }
386
-
387
- 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 "$@"