vibe-forge 0.3.6 โ 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/clear-attention.md +63 -0
- package/.claude/commands/forge.md +4 -0
- package/.claude/commands/need-help.md +77 -0
- package/.claude/commands/worker-loop.md +106 -0
- package/.claude/hooks/worker-loop.sh +141 -0
- package/.claude/scripts/setup-worker-loop.sh +45 -0
- package/.claude/settings.local.json +13 -0
- package/bin/cli.js +49 -0
- package/bin/forge-daemon.sh +122 -7
- package/bin/forge-setup.sh +43 -0
- package/bin/forge-spawn.sh +25 -7
- package/bin/forge.sh +88 -11
- package/bin/lib/agents.sh +20 -0
- package/bin/lib/config.sh +12 -0
- package/bin/lib/constants.sh +27 -0
- package/config/agents.json +18 -9
- package/context/modern-conventions.md +129 -0
- package/docs/TODO.md +121 -10
- package/package.json +1 -1
package/bin/forge-daemon.sh
CHANGED
|
@@ -113,6 +113,7 @@ safe_move_task() {
|
|
|
113
113
|
|
|
114
114
|
notify() {
|
|
115
115
|
local message="$1"
|
|
116
|
+
local urgency="${2:-normal}" # normal or urgent
|
|
116
117
|
local timestamp
|
|
117
118
|
timestamp=$(date -Iseconds)
|
|
118
119
|
|
|
@@ -125,7 +126,41 @@ notify() {
|
|
|
125
126
|
# Terminal bell (works in most terminals)
|
|
126
127
|
printf '\a'
|
|
127
128
|
|
|
128
|
-
#
|
|
129
|
+
# System toast notification for urgent messages
|
|
130
|
+
if [[ "$urgency" == "urgent" ]]; then
|
|
131
|
+
send_system_notification "$message"
|
|
132
|
+
fi
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Send system-level notification (platform-specific)
|
|
136
|
+
send_system_notification() {
|
|
137
|
+
local message="$1"
|
|
138
|
+
local title="Vibe Forge"
|
|
139
|
+
|
|
140
|
+
case "$(uname -s)" in
|
|
141
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
142
|
+
# Windows: Use PowerShell toast notification
|
|
143
|
+
powershell.exe -NoProfile -Command "
|
|
144
|
+
\$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
|
|
145
|
+
\$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
|
|
146
|
+
\$textNodes = \$template.GetElementsByTagName('text')
|
|
147
|
+
\$textNodes.Item(0).AppendChild(\$template.CreateTextNode('$title')) | Out-Null
|
|
148
|
+
\$textNodes.Item(1).AppendChild(\$template.CreateTextNode('$message')) | Out-Null
|
|
149
|
+
\$toast = [Windows.UI.Notifications.ToastNotification]::new(\$template)
|
|
150
|
+
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Vibe Forge').Show(\$toast)
|
|
151
|
+
" 2>/dev/null &
|
|
152
|
+
;;
|
|
153
|
+
Darwin)
|
|
154
|
+
# macOS: Use osascript
|
|
155
|
+
osascript -e "display notification \"$message\" with title \"$title\"" 2>/dev/null &
|
|
156
|
+
;;
|
|
157
|
+
Linux)
|
|
158
|
+
# Linux: Use notify-send if available
|
|
159
|
+
if command -v notify-send &>/dev/null; then
|
|
160
|
+
notify-send "$title" "$message" 2>/dev/null &
|
|
161
|
+
fi
|
|
162
|
+
;;
|
|
163
|
+
esac
|
|
129
164
|
}
|
|
130
165
|
|
|
131
166
|
check_new_pending_tasks() {
|
|
@@ -143,10 +178,10 @@ check_new_pending_tasks() {
|
|
|
143
178
|
# Extract task info from frontmatter safely
|
|
144
179
|
local task_id task_title assigned_to
|
|
145
180
|
|
|
146
|
-
# Use head to limit read
|
|
147
|
-
task_id=$(grep -m1 "^id:" "$task" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | head -c 100)
|
|
148
|
-
task_title=$(grep -m1 "^title:" "$task" 2>/dev/null | cut -d':' -f2- | tr -d '"' | head -c 200)
|
|
149
|
-
assigned_to=$(grep -m1 "^assigned_to:" "$task" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | head -c 50)
|
|
181
|
+
# Use head to limit read, tr to sanitize, and strip ANSI escape sequences
|
|
182
|
+
task_id=$(grep -m1 "^id:" "$task" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | tr -d '\033' | sed 's/\[[0-9;]*m//g' | head -c 100)
|
|
183
|
+
task_title=$(grep -m1 "^title:" "$task" 2>/dev/null | cut -d':' -f2- | tr -d '"' | tr -d '\033' | sed 's/\[[0-9;]*m//g' | head -c 200)
|
|
184
|
+
assigned_to=$(grep -m1 "^assigned_to:" "$task" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | tr -d '\033' | sed 's/\[[0-9;]*m//g' | head -c 50)
|
|
150
185
|
|
|
151
186
|
# Use filename as fallback
|
|
152
187
|
task_id="${task_id:-$filename}"
|
|
@@ -191,13 +226,46 @@ check_new_pending_tasks() {
|
|
|
191
226
|
done
|
|
192
227
|
}
|
|
193
228
|
|
|
229
|
+
check_attention_needed() {
|
|
230
|
+
# Check for workers needing attention (urgent notifications)
|
|
231
|
+
if [[ ! -d "$FORGE_ROOT/$TASKS_ATTENTION" ]]; then
|
|
232
|
+
return 0
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
for attention_file in "$FORGE_ROOT/$TASKS_ATTENTION"/*.md; do
|
|
236
|
+
if [[ -f "$attention_file" && ! -L "$attention_file" ]]; then
|
|
237
|
+
local filename
|
|
238
|
+
filename=$(basename "$attention_file")
|
|
239
|
+
local notified_key="attention:$filename"
|
|
240
|
+
|
|
241
|
+
if ! grep -qF "$notified_key" "$NOTIFIED_FILE" 2>/dev/null; then
|
|
242
|
+
# Extract attention info
|
|
243
|
+
local agent issue
|
|
244
|
+
agent=$(grep -m1 "^agent:" "$attention_file" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | head -c 50)
|
|
245
|
+
issue=$(grep -m1 "^##" "$attention_file" 2>/dev/null | sed 's/^## *//' | head -c 200)
|
|
246
|
+
|
|
247
|
+
agent="${agent:-Unknown}"
|
|
248
|
+
issue="${issue:-Needs attention}"
|
|
249
|
+
|
|
250
|
+
# Ring bell multiple times for attention
|
|
251
|
+
printf '\a\a\a'
|
|
252
|
+
|
|
253
|
+
# Send urgent notification with toast
|
|
254
|
+
notify "๐ $agent needs help: $issue" "urgent"
|
|
255
|
+
|
|
256
|
+
echo "$notified_key" >> "$NOTIFIED_FILE"
|
|
257
|
+
fi
|
|
258
|
+
fi
|
|
259
|
+
done
|
|
260
|
+
}
|
|
261
|
+
|
|
194
262
|
# =============================================================================
|
|
195
263
|
# Daemon Functions
|
|
196
264
|
# =============================================================================
|
|
197
265
|
|
|
198
266
|
update_state() {
|
|
199
267
|
# Count tasks in each folder (using find with -maxdepth for safety)
|
|
200
|
-
local pending in_progress completed review approved needs_changes merged
|
|
268
|
+
local pending in_progress completed review approved needs_changes merged attention
|
|
201
269
|
pending=$(find "$FORGE_ROOT/$TASKS_PENDING" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
|
|
202
270
|
in_progress=$(find "$FORGE_ROOT/$TASKS_IN_PROGRESS" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
|
|
203
271
|
completed=$(find "$FORGE_ROOT/$TASKS_COMPLETED" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
|
|
@@ -205,9 +273,16 @@ update_state() {
|
|
|
205
273
|
approved=$(find "$FORGE_ROOT/$TASKS_APPROVED" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
|
|
206
274
|
needs_changes=$(find "$FORGE_ROOT/$TASKS_NEEDS_CHANGES" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
|
|
207
275
|
merged=$(find "$FORGE_ROOT/$TASKS_MERGED" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
|
|
276
|
+
attention=$(find "$FORGE_ROOT/$TASKS_ATTENTION" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
|
|
208
277
|
|
|
209
278
|
local blocked=0
|
|
210
279
|
|
|
280
|
+
# Build attention details if any workers need help
|
|
281
|
+
local attention_details=""
|
|
282
|
+
if [[ "$attention" -gt 0 ]]; then
|
|
283
|
+
attention_details=$(build_attention_details)
|
|
284
|
+
fi
|
|
285
|
+
|
|
211
286
|
# Write state file atomically (write to temp, then move)
|
|
212
287
|
local temp_state="${STATE_FILE}.tmp.$$"
|
|
213
288
|
cat > "$temp_state" << EOF
|
|
@@ -228,12 +303,32 @@ tasks:
|
|
|
228
303
|
needs_changes: $needs_changes
|
|
229
304
|
merged: $merged
|
|
230
305
|
blocked: $blocked
|
|
306
|
+
attention_needed: $attention
|
|
231
307
|
|
|
308
|
+
$attention_details
|
|
232
309
|
last_updated: $(date -Iseconds)
|
|
233
310
|
EOF
|
|
234
311
|
mv "$temp_state" "$STATE_FILE"
|
|
235
312
|
}
|
|
236
313
|
|
|
314
|
+
build_attention_details() {
|
|
315
|
+
echo "attention:"
|
|
316
|
+
for attention_file in "$FORGE_ROOT/$TASKS_ATTENTION"/*.md; do
|
|
317
|
+
if [[ -f "$attention_file" && ! -L "$attention_file" ]]; then
|
|
318
|
+
local agent created issue
|
|
319
|
+
agent=$(grep -m1 "^agent:" "$attention_file" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | head -c 50)
|
|
320
|
+
created=$(grep -m1 "^created:" "$attention_file" 2>/dev/null | cut -d':' -f2- | tr -d ' ' | head -c 30)
|
|
321
|
+
# Get the issue line (first ## heading content or fallback)
|
|
322
|
+
issue=$(sed -n '/^## Issue/,/^##/p' "$attention_file" 2>/dev/null | grep -v "^##" | head -1 | tr -d '\n' | head -c 100)
|
|
323
|
+
issue="${issue:-Needs attention}"
|
|
324
|
+
|
|
325
|
+
echo " - agent: $agent"
|
|
326
|
+
echo " since: $created"
|
|
327
|
+
echo " issue: \"$issue\""
|
|
328
|
+
fi
|
|
329
|
+
done
|
|
330
|
+
}
|
|
331
|
+
|
|
237
332
|
route_completed_to_review() {
|
|
238
333
|
# Move completed tasks to review queue
|
|
239
334
|
for task in "$FORGE_ROOT/$TASKS_COMPLETED"/*.md; do
|
|
@@ -275,6 +370,9 @@ daemon_loop() {
|
|
|
275
370
|
# Check for new tasks and notify
|
|
276
371
|
check_new_pending_tasks
|
|
277
372
|
|
|
373
|
+
# Check for workers needing attention (urgent)
|
|
374
|
+
check_attention_needed
|
|
375
|
+
|
|
278
376
|
# Route tasks
|
|
279
377
|
route_completed_to_review
|
|
280
378
|
route_approved_to_merged
|
|
@@ -334,6 +432,7 @@ cmd_start() {
|
|
|
334
432
|
mkdir -p "$FORGE_ROOT/$TASKS_APPROVED"
|
|
335
433
|
mkdir -p "$FORGE_ROOT/$TASKS_NEEDS_CHANGES"
|
|
336
434
|
mkdir -p "$FORGE_ROOT/$TASKS_MERGED"
|
|
435
|
+
mkdir -p "$FORGE_ROOT/$TASKS_ATTENTION"
|
|
337
436
|
|
|
338
437
|
# Start daemon in background
|
|
339
438
|
daemon_loop &
|
|
@@ -394,11 +493,27 @@ cmd_status() {
|
|
|
394
493
|
|
|
395
494
|
if [[ -f "$STATE_FILE" ]]; then
|
|
396
495
|
echo "Task Counts:"
|
|
397
|
-
grep -E "pending:|in_progress:|completed:|in_review:|approved:|needs_changes:|merged:" "$STATE_FILE" | sed 's/^/ /'
|
|
496
|
+
grep -E "pending:|in_progress:|completed:|in_review:|approved:|needs_changes:|merged:|attention_needed:" "$STATE_FILE" | sed 's/^/ /'
|
|
398
497
|
fi
|
|
399
498
|
|
|
400
499
|
echo ""
|
|
401
500
|
|
|
501
|
+
# Show attention-needed workers prominently
|
|
502
|
+
local attention_count
|
|
503
|
+
attention_count=$(find "$FORGE_ROOT/$TASKS_ATTENTION" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
|
|
504
|
+
if [[ "$attention_count" -gt 0 ]]; then
|
|
505
|
+
echo -e "${RED}๐ ATTENTION NEEDED:${NC}"
|
|
506
|
+
for attention_file in "$FORGE_ROOT/$TASKS_ATTENTION"/*.md; do
|
|
507
|
+
if [[ -f "$attention_file" && ! -L "$attention_file" ]]; then
|
|
508
|
+
local agent issue
|
|
509
|
+
agent=$(grep -m1 "^agent:" "$attention_file" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | head -c 50)
|
|
510
|
+
issue=$(sed -n '/^## Issue/,/^##/p' "$attention_file" 2>/dev/null | grep -v "^##" | head -1 | head -c 80)
|
|
511
|
+
echo -e " ${YELLOW}$agent${NC}: $issue"
|
|
512
|
+
fi
|
|
513
|
+
done
|
|
514
|
+
echo ""
|
|
515
|
+
fi
|
|
516
|
+
|
|
402
517
|
# Show recent notifications
|
|
403
518
|
if [[ -f "$NOTIFY_FILE" ]]; then
|
|
404
519
|
local notify_count
|
package/bin/forge-setup.sh
CHANGED
|
@@ -304,6 +304,48 @@ configure_daemon() {
|
|
|
304
304
|
fi
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
# =============================================================================
|
|
308
|
+
# STEP 7b: Configure Worker Loop (Persistent Mode)
|
|
309
|
+
# =============================================================================
|
|
310
|
+
|
|
311
|
+
configure_worker_loop() {
|
|
312
|
+
echo ""
|
|
313
|
+
echo "Worker Loop Configuration"
|
|
314
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
315
|
+
echo ""
|
|
316
|
+
echo "Worker Loop keeps agents running continuously."
|
|
317
|
+
echo "When a worker finishes a task, it automatically checks for new work"
|
|
318
|
+
echo "instead of exiting. Great for longer coding sessions."
|
|
319
|
+
echo ""
|
|
320
|
+
|
|
321
|
+
local enable_worker_loop="n"
|
|
322
|
+
|
|
323
|
+
if [[ "$NON_INTERACTIVE" == "false" ]]; then
|
|
324
|
+
read -p "Enable persistent worker mode? (y/N): " choice
|
|
325
|
+
if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
|
|
326
|
+
enable_worker_loop="y"
|
|
327
|
+
fi
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
# Update config with worker loop preference
|
|
331
|
+
local config_file="$FORGE_ROOT/.forge/config.json"
|
|
332
|
+
if [[ -f "$config_file" ]]; then
|
|
333
|
+
# Add worker_loop_enabled to config
|
|
334
|
+
sed -i 's/"daemon_enabled": \(true\|false\)/"daemon_enabled": \1,\n "worker_loop_enabled": '"$( [[ "$enable_worker_loop" == "y" ]] && echo "true" || echo "false" )"'/' "$config_file" 2>/dev/null || true
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
if [[ "$enable_worker_loop" == "y" ]]; then
|
|
338
|
+
echo ""
|
|
339
|
+
echo -e "${GREEN}โ
Worker Loop enabled${NC}"
|
|
340
|
+
echo " Workers will keep running and check for new tasks"
|
|
341
|
+
else
|
|
342
|
+
echo ""
|
|
343
|
+
echo -e "${YELLOW}โน๏ธ Worker Loop disabled${NC}"
|
|
344
|
+
echo " Workers will exit after completing their tasks"
|
|
345
|
+
echo " Enable later with: forge config worker-loop on"
|
|
346
|
+
fi
|
|
347
|
+
}
|
|
348
|
+
|
|
307
349
|
# =============================================================================
|
|
308
350
|
# STEP 8: Install Slash Command
|
|
309
351
|
# =============================================================================
|
|
@@ -476,6 +518,7 @@ main() {
|
|
|
476
518
|
if validate_setup; then
|
|
477
519
|
configure_terminal
|
|
478
520
|
configure_daemon
|
|
521
|
+
configure_worker_loop
|
|
479
522
|
install_slash_command
|
|
480
523
|
create_project_context
|
|
481
524
|
setup_complete
|
package/bin/forge-spawn.sh
CHANGED
|
@@ -31,7 +31,9 @@ source "$SCRIPT_DIR/lib/agents.sh"
|
|
|
31
31
|
|
|
32
32
|
# Load agent configuration from JSON if available
|
|
33
33
|
if [[ -f "$FORGE_ROOT/$AGENTS_CONFIG" ]]; then
|
|
34
|
-
load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null
|
|
34
|
+
if ! load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null; then
|
|
35
|
+
log_warn "Failed to load agents.json, using fallback defaults"
|
|
36
|
+
fi
|
|
35
37
|
fi
|
|
36
38
|
|
|
37
39
|
# =============================================================================
|
|
@@ -44,9 +46,17 @@ spawn_windows_terminal() {
|
|
|
44
46
|
log_info "Spawning $agent in Windows Terminal..."
|
|
45
47
|
|
|
46
48
|
if command -v wt &> /dev/null || command -v wt.exe &> /dev/null; then
|
|
47
|
-
# Get display
|
|
48
|
-
local display_name
|
|
49
|
+
# Get display info for tab
|
|
50
|
+
local display_name icon tab_color
|
|
49
51
|
display_name=$(get_agent_display_name "$agent")
|
|
52
|
+
icon=$(get_agent_icon "$agent")
|
|
53
|
+
tab_color=$(get_agent_tab_color "$agent")
|
|
54
|
+
|
|
55
|
+
# Build tab title with icon
|
|
56
|
+
local tab_title="$display_name"
|
|
57
|
+
if [[ -n "$icon" ]]; then
|
|
58
|
+
tab_title="$icon $display_name"
|
|
59
|
+
fi
|
|
50
60
|
|
|
51
61
|
# Convert FORGE_ROOT to Windows path for wt.exe
|
|
52
62
|
# Git Bash paths like /c/foo become C:/foo
|
|
@@ -57,15 +67,21 @@ spawn_windows_terminal() {
|
|
|
57
67
|
win_forge_root="$FORGE_ROOT"
|
|
58
68
|
fi
|
|
59
69
|
|
|
70
|
+
# Build wt command with optional tab color
|
|
71
|
+
local wt_args=(-w 0 new-tab --title "$tab_title")
|
|
72
|
+
if [[ -n "$tab_color" ]]; then
|
|
73
|
+
wt_args+=(--tabColor "$tab_color")
|
|
74
|
+
fi
|
|
75
|
+
|
|
60
76
|
# Windows Terminal: open new tab with forge command
|
|
61
77
|
# SECURITY: $agent has already been validated through resolve_agent
|
|
62
78
|
if [[ -n "$GIT_BASH_PATH" ]]; then
|
|
63
79
|
local bash_path="${GIT_BASH_PATH//\//\\}"
|
|
64
|
-
wt.exe
|
|
80
|
+
wt.exe "${wt_args[@]}" "$bash_path" -c "cd \"$win_forge_root\" && ./bin/forge.sh start \"$agent\""
|
|
65
81
|
else
|
|
66
|
-
wt.exe
|
|
82
|
+
wt.exe "${wt_args[@]}" bash -c "cd \"$win_forge_root\" && ./bin/forge.sh start \"$agent\""
|
|
67
83
|
fi
|
|
68
|
-
log_success "$
|
|
84
|
+
log_success "$tab_title spawned in new Windows Terminal tab"
|
|
69
85
|
else
|
|
70
86
|
log_error "wt command not found."
|
|
71
87
|
echo "Make sure Windows Terminal is installed."
|
|
@@ -123,7 +139,9 @@ main() {
|
|
|
123
139
|
require_forge_config "$FORGE_ROOT"
|
|
124
140
|
|
|
125
141
|
echo ""
|
|
126
|
-
|
|
142
|
+
local icon
|
|
143
|
+
icon=$(get_agent_icon "$resolved")
|
|
144
|
+
log_header "${icon:-๐ฅ} Forge Spawn: $(get_agent_display_name "$resolved")"
|
|
127
145
|
if [[ "$agent" != "$resolved" ]]; then
|
|
128
146
|
echo " (resolved from '$agent')"
|
|
129
147
|
fi
|
package/bin/forge.sh
CHANGED
|
@@ -41,7 +41,9 @@ source "$SCRIPT_DIR/lib/agents.sh"
|
|
|
41
41
|
# Load agent configuration from JSON if available
|
|
42
42
|
# This overwrites the fallback values in constants.sh
|
|
43
43
|
if [[ -f "$FORGE_ROOT/$AGENTS_CONFIG" ]]; then
|
|
44
|
-
load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null
|
|
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
|
|
45
47
|
fi
|
|
46
48
|
|
|
47
49
|
# =============================================================================
|
|
@@ -116,7 +118,13 @@ On startup, you MUST immediately display the team assembly welcome as shown in t
|
|
|
116
118
|
|
|
117
119
|
# Startup Instructions
|
|
118
120
|
|
|
119
|
-
On startup:
|
|
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.
|
|
120
128
|
"
|
|
121
129
|
fi
|
|
122
130
|
|
|
@@ -233,6 +241,73 @@ cmd_daemon() {
|
|
|
233
241
|
esac
|
|
234
242
|
}
|
|
235
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
|
+
|
|
236
311
|
cmd_help() {
|
|
237
312
|
echo ""
|
|
238
313
|
log_header "๐ฅ Vibe Forge"
|
|
@@ -247,17 +322,15 @@ cmd_help() {
|
|
|
247
322
|
echo " status Show current forge status"
|
|
248
323
|
echo " test Validate setup is working"
|
|
249
324
|
echo " daemon <action> Manage background daemon (start|stop|status|notifications|clear)"
|
|
325
|
+
echo " config <setting> View or change configuration settings"
|
|
250
326
|
echo " help Show this help message"
|
|
251
327
|
echo ""
|
|
252
|
-
|
|
253
|
-
echo "
|
|
254
|
-
echo "
|
|
255
|
-
echo "
|
|
256
|
-
echo "
|
|
257
|
-
echo "
|
|
258
|
-
echo " herald (release, deploy) - Release Manager"
|
|
259
|
-
echo " ember (devops, ops, infra) - DevOps"
|
|
260
|
-
echo " aegis (security, sec, appsec) - Security"
|
|
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"
|
|
261
334
|
echo ""
|
|
262
335
|
echo "Examples:"
|
|
263
336
|
echo " forge Start Planning Hub"
|
|
@@ -298,6 +371,10 @@ main() {
|
|
|
298
371
|
shift
|
|
299
372
|
cmd_daemon "$@"
|
|
300
373
|
;;
|
|
374
|
+
"config")
|
|
375
|
+
shift
|
|
376
|
+
cmd_config "$@"
|
|
377
|
+
;;
|
|
301
378
|
"help"|"--help"|"-h")
|
|
302
379
|
cmd_help
|
|
303
380
|
;;
|
package/bin/lib/agents.sh
CHANGED
|
@@ -155,3 +155,23 @@ get_agent_role() {
|
|
|
155
155
|
|
|
156
156
|
echo "${AGENT_ROLES[$canonical]:-}"
|
|
157
157
|
}
|
|
158
|
+
|
|
159
|
+
# get_agent_icon AGENT
|
|
160
|
+
# Returns the icon/emoji for an agent
|
|
161
|
+
get_agent_icon() {
|
|
162
|
+
local agent="$1"
|
|
163
|
+
local canonical
|
|
164
|
+
canonical=$(resolve_agent "$agent") || return 1
|
|
165
|
+
|
|
166
|
+
echo "${AGENT_ICONS[$canonical]:-}"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# get_agent_tab_color AGENT
|
|
170
|
+
# Returns the Windows Terminal tab color for an agent (hex format)
|
|
171
|
+
get_agent_tab_color() {
|
|
172
|
+
local agent="$1"
|
|
173
|
+
local canonical
|
|
174
|
+
canonical=$(resolve_agent "$agent") || return 1
|
|
175
|
+
|
|
176
|
+
echo "${AGENT_TAB_COLORS[$canonical]:-}"
|
|
177
|
+
}
|
package/bin/lib/config.sh
CHANGED
|
@@ -78,6 +78,18 @@ load_agents_from_json() {
|
|
|
78
78
|
console.log(`AGENT_PERSONALITY_FILES["${canonical}"]="${pfile}"`);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// Output AGENT_ICONS assignments
|
|
82
|
+
for (const [canonical, info] of Object.entries(agents)) {
|
|
83
|
+
const icon = info.icon || "";
|
|
84
|
+
console.log(`AGENT_ICONS["${canonical}"]="${icon}"`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Output AGENT_TAB_COLORS assignments
|
|
88
|
+
for (const [canonical, info] of Object.entries(agents)) {
|
|
89
|
+
const tabColor = info.tab_color || "";
|
|
90
|
+
console.log(`AGENT_TAB_COLORS["${canonical}"]="${tabColor}"`);
|
|
91
|
+
}
|
|
92
|
+
|
|
81
93
|
} catch (e) {
|
|
82
94
|
console.error("Error parsing agents.json:", e.message);
|
|
83
95
|
process.exit(1);
|
package/bin/lib/constants.sh
CHANGED
|
@@ -22,6 +22,7 @@ TASKS_REVIEW="$TASKS_DIR/review"
|
|
|
22
22
|
TASKS_APPROVED="$TASKS_DIR/approved"
|
|
23
23
|
TASKS_NEEDS_CHANGES="$TASKS_DIR/needs-changes"
|
|
24
24
|
TASKS_MERGED="$TASKS_DIR/merged"
|
|
25
|
+
TASKS_ATTENTION="$TASKS_DIR/attention"
|
|
25
26
|
CONTEXT_DIR="context"
|
|
26
27
|
AGENTS_DIR="agents"
|
|
27
28
|
|
|
@@ -141,3 +142,29 @@ declare -A AGENT_PERSONALITY_FILES=(
|
|
|
141
142
|
["aegis"]="agents/aegis/personality.md"
|
|
142
143
|
["hub"]="agents/planning-hub/personality.md"
|
|
143
144
|
)
|
|
145
|
+
|
|
146
|
+
# Agent icons
|
|
147
|
+
declare -A AGENT_ICONS=(
|
|
148
|
+
["anvil"]="๐จ"
|
|
149
|
+
["furnace"]="๐ฅ"
|
|
150
|
+
["crucible"]="๐งช"
|
|
151
|
+
["sentinel"]="๐ก๏ธ"
|
|
152
|
+
["scribe"]="๐"
|
|
153
|
+
["herald"]="๐ฏ"
|
|
154
|
+
["ember"]="โ๏ธ"
|
|
155
|
+
["aegis"]="๐"
|
|
156
|
+
["hub"]="๐ฅ"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Agent tab colors for Windows Terminal (hex format)
|
|
160
|
+
declare -A AGENT_TAB_COLORS=(
|
|
161
|
+
["anvil"]="#3B82F6"
|
|
162
|
+
["furnace"]="#EF4444"
|
|
163
|
+
["crucible"]="#10B981"
|
|
164
|
+
["sentinel"]="#8B5CF6"
|
|
165
|
+
["scribe"]="#F59E0B"
|
|
166
|
+
["herald"]="#EC4899"
|
|
167
|
+
["ember"]="#F97316"
|
|
168
|
+
["aegis"]="#06B6D4"
|
|
169
|
+
["hub"]="#FF6B35"
|
|
170
|
+
)
|
package/config/agents.json
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"role": "Frontend Developer",
|
|
7
7
|
"aliases": ["frontend", "ui", "fe"],
|
|
8
8
|
"type": "worker",
|
|
9
|
-
"personality_file": "agents/anvil/personality.md"
|
|
9
|
+
"personality_file": "agents/anvil/personality.md",
|
|
10
|
+
"tab_color": "#3B82F6"
|
|
10
11
|
},
|
|
11
12
|
"furnace": {
|
|
12
13
|
"name": "Furnace",
|
|
@@ -14,7 +15,8 @@
|
|
|
14
15
|
"role": "Backend Developer",
|
|
15
16
|
"aliases": ["backend", "api", "be"],
|
|
16
17
|
"type": "worker",
|
|
17
|
-
"personality_file": "agents/furnace/personality.md"
|
|
18
|
+
"personality_file": "agents/furnace/personality.md",
|
|
19
|
+
"tab_color": "#EF4444"
|
|
18
20
|
},
|
|
19
21
|
"crucible": {
|
|
20
22
|
"name": "Crucible",
|
|
@@ -22,7 +24,8 @@
|
|
|
22
24
|
"role": "Tester / QA",
|
|
23
25
|
"aliases": ["test", "testing", "qa", "tester"],
|
|
24
26
|
"type": "worker",
|
|
25
|
-
"personality_file": "agents/crucible/personality.md"
|
|
27
|
+
"personality_file": "agents/crucible/personality.md",
|
|
28
|
+
"tab_color": "#10B981"
|
|
26
29
|
},
|
|
27
30
|
"sentinel": {
|
|
28
31
|
"name": "Sentinel",
|
|
@@ -30,7 +33,8 @@
|
|
|
30
33
|
"role": "Code Reviewer",
|
|
31
34
|
"aliases": ["review", "reviewer", "cr"],
|
|
32
35
|
"type": "core",
|
|
33
|
-
"personality_file": "agents/sentinel/personality.md"
|
|
36
|
+
"personality_file": "agents/sentinel/personality.md",
|
|
37
|
+
"tab_color": "#8B5CF6"
|
|
34
38
|
},
|
|
35
39
|
"scribe": {
|
|
36
40
|
"name": "Scribe",
|
|
@@ -38,7 +42,8 @@
|
|
|
38
42
|
"role": "Documentation",
|
|
39
43
|
"aliases": ["docs", "documentation", "doc"],
|
|
40
44
|
"type": "worker",
|
|
41
|
-
"personality_file": "agents/scribe/personality.md"
|
|
45
|
+
"personality_file": "agents/scribe/personality.md",
|
|
46
|
+
"tab_color": "#F59E0B"
|
|
42
47
|
},
|
|
43
48
|
"herald": {
|
|
44
49
|
"name": "Herald",
|
|
@@ -46,7 +51,8 @@
|
|
|
46
51
|
"role": "Release Manager",
|
|
47
52
|
"aliases": ["release", "deploy", "deployment"],
|
|
48
53
|
"type": "worker",
|
|
49
|
-
"personality_file": "agents/herald/personality.md"
|
|
54
|
+
"personality_file": "agents/herald/personality.md",
|
|
55
|
+
"tab_color": "#EC4899"
|
|
50
56
|
},
|
|
51
57
|
"ember": {
|
|
52
58
|
"name": "Ember",
|
|
@@ -54,7 +60,8 @@
|
|
|
54
60
|
"role": "DevOps",
|
|
55
61
|
"aliases": ["devops", "ops", "infra", "infrastructure"],
|
|
56
62
|
"type": "specialist",
|
|
57
|
-
"personality_file": "agents/ember/personality.md"
|
|
63
|
+
"personality_file": "agents/ember/personality.md",
|
|
64
|
+
"tab_color": "#F97316"
|
|
58
65
|
},
|
|
59
66
|
"aegis": {
|
|
60
67
|
"name": "Aegis",
|
|
@@ -62,7 +69,8 @@
|
|
|
62
69
|
"role": "Security",
|
|
63
70
|
"aliases": ["security", "sec", "appsec"],
|
|
64
71
|
"type": "specialist",
|
|
65
|
-
"personality_file": "agents/aegis/personality.md"
|
|
72
|
+
"personality_file": "agents/aegis/personality.md",
|
|
73
|
+
"tab_color": "#06B6D4"
|
|
66
74
|
},
|
|
67
75
|
"hub": {
|
|
68
76
|
"name": "Planning Hub",
|
|
@@ -70,7 +78,8 @@
|
|
|
70
78
|
"role": "Planning & Coordination",
|
|
71
79
|
"aliases": ["planning", "master", "forge-master"],
|
|
72
80
|
"type": "core",
|
|
73
|
-
"personality_file": "agents/planning-hub/personality.md"
|
|
81
|
+
"personality_file": "agents/planning-hub/personality.md",
|
|
82
|
+
"tab_color": "#FF6B35"
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
85
|
}
|