@sugar-crash-studios/vibe-forge 0.4.0

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 (201) hide show
  1. package/.claude/commands/clear-attention.md +63 -0
  2. package/.claude/commands/compact-context.md +52 -0
  3. package/.claude/commands/configure-vcs.md +102 -0
  4. package/.claude/commands/forge.md +171 -0
  5. package/.claude/commands/need-help.md +77 -0
  6. package/.claude/commands/update-status.md +64 -0
  7. package/.claude/commands/worker-loop.md +106 -0
  8. package/.claude/hooks/worker-loop.js +198 -0
  9. package/.claude/scripts/setup-worker-loop.sh +45 -0
  10. package/.claude/settings.local.json +46 -0
  11. package/LICENSE +21 -0
  12. package/README.md +238 -0
  13. package/agents/aegis/personality.md +294 -0
  14. package/agents/anvil/personality.md +276 -0
  15. package/agents/architect/personality.md +258 -0
  16. package/agents/crucible/personality.md +360 -0
  17. package/agents/ember/personality.md +291 -0
  18. package/agents/forge-master/capabilities.md +144 -0
  19. package/agents/forge-master/context-template.md +128 -0
  20. package/agents/forge-master/personality.md +138 -0
  21. package/agents/furnace/personality.md +340 -0
  22. package/agents/herald/personality.md +247 -0
  23. package/agents/loki/personality.md +108 -0
  24. package/agents/oracle/personality.md +283 -0
  25. package/agents/pixel/personality.md +113 -0
  26. package/agents/planning-hub/personality.md +320 -0
  27. package/agents/scribe/personality.md +251 -0
  28. package/agents/temper/personality.md +218 -0
  29. package/bin/cli.js +375 -0
  30. package/bin/dashboard/api/agents.js +333 -0
  31. package/bin/dashboard/api/dispatch.js +483 -0
  32. package/bin/dashboard/api/tasks.js +416 -0
  33. package/bin/dashboard/frontend/index.html +13 -0
  34. package/bin/dashboard/frontend/package.json +16 -0
  35. package/bin/dashboard/frontend/src/App.svelte +222 -0
  36. package/bin/dashboard/frontend/src/app.css +1777 -0
  37. package/bin/dashboard/frontend/src/lib/components/AgentCard.svelte +60 -0
  38. package/bin/dashboard/frontend/src/lib/components/AgentsPanel.svelte +57 -0
  39. package/bin/dashboard/frontend/src/lib/components/DispatchModal.svelte +180 -0
  40. package/bin/dashboard/frontend/src/lib/components/Footer.svelte +33 -0
  41. package/bin/dashboard/frontend/src/lib/components/Header.svelte +84 -0
  42. package/bin/dashboard/frontend/src/lib/components/IssueCard.svelte +33 -0
  43. package/bin/dashboard/frontend/src/lib/components/IssuesPanel.svelte +73 -0
  44. package/bin/dashboard/frontend/src/lib/components/KeyboardShortcutsModal.svelte +108 -0
  45. package/bin/dashboard/frontend/src/lib/components/MobileTabs.svelte +52 -0
  46. package/bin/dashboard/frontend/src/lib/components/NotificationCard.svelte +60 -0
  47. package/bin/dashboard/frontend/src/lib/components/NotificationsPanel.svelte +44 -0
  48. package/bin/dashboard/frontend/src/lib/components/TaskCard.svelte +63 -0
  49. package/bin/dashboard/frontend/src/lib/components/TasksPanel.svelte +82 -0
  50. package/bin/dashboard/frontend/src/lib/components/Toast.svelte +45 -0
  51. package/bin/dashboard/frontend/src/lib/stores/agents.js +34 -0
  52. package/bin/dashboard/frontend/src/lib/stores/issues.js +54 -0
  53. package/bin/dashboard/frontend/src/lib/stores/notifications.js +48 -0
  54. package/bin/dashboard/frontend/src/lib/stores/tasks.js +63 -0
  55. package/bin/dashboard/frontend/src/lib/stores/theme.js +33 -0
  56. package/bin/dashboard/frontend/src/lib/stores/toast.js +35 -0
  57. package/bin/dashboard/frontend/src/lib/stores/ui.js +25 -0
  58. package/bin/dashboard/frontend/src/lib/stores/voice.js +275 -0
  59. package/bin/dashboard/frontend/src/lib/stores/websocket.js +295 -0
  60. package/bin/dashboard/frontend/src/lib/utils/api.js +101 -0
  61. package/bin/dashboard/frontend/src/lib/utils/formatters.js +54 -0
  62. package/bin/dashboard/frontend/src/main.js +9 -0
  63. package/bin/dashboard/frontend/svelte.config.js +5 -0
  64. package/bin/dashboard/frontend/vite.config.js +20 -0
  65. package/bin/dashboard/public/assets/index-DnfVj9Ce.css +1 -0
  66. package/bin/dashboard/public/assets/index-Ze5h0kXQ.js +2 -0
  67. package/bin/dashboard/public/index.html +14 -0
  68. package/bin/dashboard/server.js +566 -0
  69. package/bin/forge-daemon.sh +463 -0
  70. package/bin/forge-setup.sh +645 -0
  71. package/bin/forge-spawn.sh +164 -0
  72. package/bin/forge.cmd +83 -0
  73. package/bin/forge.sh +533 -0
  74. package/bin/lib/agents.sh +177 -0
  75. package/bin/lib/colors.sh +44 -0
  76. package/bin/lib/config.sh +347 -0
  77. package/bin/lib/constants.sh +241 -0
  78. package/bin/lib/daemon/display.sh +128 -0
  79. package/bin/lib/daemon/notifications.sh +263 -0
  80. package/bin/lib/daemon/routing.sh +77 -0
  81. package/bin/lib/daemon/state.sh +115 -0
  82. package/bin/lib/daemon/sync.sh +95 -0
  83. package/bin/lib/database.sh +310 -0
  84. package/bin/lib/heimdall-setup.js +113 -0
  85. package/bin/lib/heimdall.js +265 -0
  86. package/bin/lib/json.sh +264 -0
  87. package/bin/lib/terminal.js +451 -0
  88. package/bin/lib/util.sh +126 -0
  89. package/bin/lib/vcs.js +349 -0
  90. package/config/agent-manifest.yaml +203 -0
  91. package/config/agents.json +168 -0
  92. package/config/task-template.md +159 -0
  93. package/config/task-types.yaml +106 -0
  94. package/context/agent-status/aegis.json +7 -0
  95. package/context/agent-status/anvil.json +7 -0
  96. package/context/agent-status/architect.json +7 -0
  97. package/context/agent-status/crucible.json +7 -0
  98. package/context/agent-status/ember.json +7 -0
  99. package/context/agent-status/furnace.json +7 -0
  100. package/context/agent-status/loki.json +7 -0
  101. package/context/agent-status/oracle.json +7 -0
  102. package/context/agent-status/pixel.json +7 -0
  103. package/context/agent-status/planning-hub.json +7 -0
  104. package/context/agent-status/scribe.json +7 -0
  105. package/context/agent-status/temper.json +7 -0
  106. package/context/feature-brainstorm.md +426 -0
  107. package/context/forge-state.yaml +19 -0
  108. package/context/modern-conventions.md +129 -0
  109. package/context/project-context-template.md +122 -0
  110. package/context/project-context.md +122 -0
  111. package/docs/TODO.md +150 -0
  112. package/docs/agents.md +409 -0
  113. package/docs/architecture/decisions/ADR-001-daemon-modularization.md +122 -0
  114. package/docs/architecture/vibe-lab-integration.md +684 -0
  115. package/docs/architecture.md +194 -0
  116. package/docs/bmad-gap-analysis-2026-03-31.md +444 -0
  117. package/docs/cleanup-workflow.md +329 -0
  118. package/docs/commands.md +451 -0
  119. package/docs/dashboard-mockup.html +989 -0
  120. package/docs/getting-started.md +261 -0
  121. package/docs/integration/forge-ownership-policy.md +112 -0
  122. package/docs/npm-publishing.md +132 -0
  123. package/docs/roadmap-2026.md +519 -0
  124. package/docs/security.md +144 -0
  125. package/docs/wireframes/dashboard-mvp.md +1164 -0
  126. package/docs/workflows/README.md +32 -0
  127. package/docs/workflows/azure-devops.md +108 -0
  128. package/docs/workflows/bitbucket.md +104 -0
  129. package/docs/workflows/git-only.md +130 -0
  130. package/docs/workflows/gitea.md +168 -0
  131. package/docs/workflows/github.md +103 -0
  132. package/docs/workflows/gitlab.md +105 -0
  133. package/docs/workflows.md +454 -0
  134. package/package.json +73 -0
  135. package/tasks/completed/ARCH-001-duplicate-agent-config.md +121 -0
  136. package/tasks/completed/ARCH-002-mixed-bash-node-implementation.md +88 -0
  137. package/tasks/completed/ARCH-003-worker-loop-hook-duplication.md +77 -0
  138. package/tasks/completed/ARCH-009-test-organization.md +78 -0
  139. package/tasks/completed/ARCH-011-jq-vs-nodejs-json.md +94 -0
  140. package/tasks/completed/ARCH-012-tmp-files-in-root.md +71 -0
  141. package/tasks/completed/ARCH-013-exit-code-constants.md +65 -0
  142. package/tasks/completed/ARCH-014-sed-incompatibility.md +96 -0
  143. package/tasks/completed/ARCH-015-docs-todo-tracking.md +83 -0
  144. package/tasks/completed/BUG-dash-001-tasks-filter-error.md +31 -0
  145. package/tasks/completed/BUG-dash-002-agents-unknown.md +41 -0
  146. package/tasks/completed/CLEAN-001.md +38 -0
  147. package/tasks/completed/CLEAN-002.md +43 -0
  148. package/tasks/completed/CLEAN-003.md +47 -0
  149. package/tasks/completed/CLEAN-004.md +56 -0
  150. package/tasks/completed/CLEAN-005.md +75 -0
  151. package/tasks/completed/CLEAN-006.md +47 -0
  152. package/tasks/completed/CLEAN-007.md +34 -0
  153. package/tasks/completed/CLEAN-008.md +49 -0
  154. package/tasks/completed/CLEAN-012.md +58 -0
  155. package/tasks/completed/CLEAN-013.md +45 -0
  156. package/tasks/completed/FEATURE-001a-dashboard-wireframes.md +162 -0
  157. package/tasks/completed/IMPL-007a-daemon-notifications-module.md +82 -0
  158. package/tasks/completed/IMPL-007b-daemon-sync-module.md +71 -0
  159. package/tasks/completed/IMPL-007c-daemon-state-module.md +80 -0
  160. package/tasks/completed/IMPL-007d-daemon-routing-module.md +77 -0
  161. package/tasks/completed/IMPL-007e-daemon-display-module.md +77 -0
  162. package/tasks/completed/IMPL-007f-daemon-integration.md +124 -0
  163. package/tasks/completed/PLAT-1-heimdall.md +420 -0
  164. package/tasks/completed/SEC-001-sql-injection-fix.md +58 -0
  165. package/tasks/completed/SEC-002-notification-injection-fix.md +45 -0
  166. package/tasks/completed/SEC-003-eval-injection-fix.md +54 -0
  167. package/tasks/completed/SEC-004-pid-race-condition-fix.md +49 -0
  168. package/tasks/completed/SEC-005-worker-loop-path-fix.md +51 -0
  169. package/tasks/completed/SEC-006-eval-agent-names.md +55 -0
  170. package/tasks/completed/SEC-007-spawn-escaping.md +67 -0
  171. package/tasks/completed/TASK-DASH-001-server-infrastructure.md +185 -0
  172. package/tasks/completed/TASK-anvil-001-dashboard-frontend.md +133 -0
  173. package/tasks/completed/review-bmad-aegis.md +89 -0
  174. package/tasks/completed/review-bmad-anvil.md +80 -0
  175. package/tasks/completed/review-bmad-crucible.md +81 -0
  176. package/tasks/completed/review-bmad-ember.md +90 -0
  177. package/tasks/completed/review-bmad-furnace.md +79 -0
  178. package/tasks/completed/review-bmad-pixel.md +82 -0
  179. package/tasks/completed/review-bmad-scribe.md +92 -0
  180. package/tasks/completed/review-bmad-sentinel.md +83 -0
  181. package/tasks/pending/ARCH-004-git-bash-detection-duplication.md +72 -0
  182. package/tasks/pending/ARCH-005-missing-src-directory.md +95 -0
  183. package/tasks/pending/ARCH-006-task-template-location.md +64 -0
  184. package/tasks/pending/ARCH-008-forge-master-vs-hub.md +81 -0
  185. package/tasks/pending/ARCH-010-missing-index-files.md +84 -0
  186. package/tasks/pending/CLEAN-009.md +31 -0
  187. package/tasks/pending/CLEAN-010.md +30 -0
  188. package/tasks/pending/CLEAN-011.md +30 -0
  189. package/tasks/pending/CLEAN-014.md +32 -0
  190. package/tasks/pending/DESIGN-dash-001-layout-review.md +45 -0
  191. package/tasks/pending/FEATURE-001-dashboard-mvp.md +268 -0
  192. package/tasks/review/ARCH-007-daemon-monolith.md +162 -0
  193. package/tasks/review/bmad-review-aegis.md +349 -0
  194. package/tasks/review/bmad-review-anvil.md +259 -0
  195. package/tasks/review/bmad-review-crucible.md +277 -0
  196. package/tasks/review/bmad-review-ember.md +307 -0
  197. package/tasks/review/bmad-review-furnace.md +285 -0
  198. package/tasks/review/bmad-review-pixel.md +329 -0
  199. package/tasks/review/bmad-review-scribe.md +361 -0
  200. package/tasks/review/bmad-review-sentinel.md +242 -0
  201. package/tasks/review/task-001.md +78 -0
package/bin/forge.sh ADDED
@@ -0,0 +1,533 @@
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
+ # 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
+ # Alias for doctor
165
+ cmd_doctor
166
+ }
167
+
168
+ cmd_doctor() {
169
+ echo ""
170
+ log_header "🩺 Forge Doctor"
171
+ echo ""
172
+
173
+ local issues=0
174
+
175
+ # --- Dependencies ---
176
+ echo "Dependencies"
177
+ echo "────────────────────────────────────────────────"
178
+
179
+ if claude --version &> /dev/null; then
180
+ local claude_ver
181
+ claude_ver=$(claude --version 2>&1 | head -1)
182
+ log_success "Claude Code: $claude_ver"
183
+ else
184
+ log_error "Claude Code: not found"
185
+ echo " Install from: https://claude.ai/download"
186
+ issues=$((issues + 1))
187
+ fi
188
+
189
+ if command -v node &> /dev/null; then
190
+ local node_ver
191
+ node_ver=$(node --version 2>&1)
192
+ log_success "Node.js: $node_ver"
193
+ # Warn if below Node 18
194
+ local node_major
195
+ node_major=$(node --version | sed 's/v\([0-9]*\).*/\1/')
196
+ if [[ "$node_major" -lt 18 ]]; then
197
+ log_warn "Node.js $node_ver is below recommended v18+"
198
+ fi
199
+ else
200
+ log_error "Node.js: not found"
201
+ issues=$((issues + 1))
202
+ fi
203
+
204
+ if command -v git &> /dev/null; then
205
+ local git_ver
206
+ git_ver=$(git --version 2>&1)
207
+ log_success "Git: $git_ver"
208
+ else
209
+ log_error "Git: not found"
210
+ issues=$((issues + 1))
211
+ fi
212
+
213
+ if command -v sqlite3 &> /dev/null; then
214
+ local sq_ver
215
+ sq_ver=$(sqlite3 --version 2>&1 | head -1)
216
+ log_success "SQLite3: $sq_ver"
217
+ else
218
+ log_warn "SQLite3: not found (daemon metrics disabled)"
219
+ fi
220
+
221
+ # --- Forge Config ---
222
+ echo ""
223
+ echo "Configuration"
224
+ echo "────────────────────────────────────────────────"
225
+
226
+ local config_file="$FORGE_ROOT/.forge/config.json"
227
+ local local_config_file="$FORGE_ROOT/.forge/config.local.json"
228
+
229
+ if [[ -f "$config_file" ]]; then
230
+ log_success "config.json: found"
231
+ local platform terminal
232
+ platform=$(json_get_string "$config_file" "platform" 2>/dev/null || echo "unknown")
233
+ terminal=$(json_get_string "$config_file" "terminal_type" 2>/dev/null || echo "unknown")
234
+ echo " Platform: $platform | Terminal: $terminal"
235
+ else
236
+ log_error "config.json: not found — run 'forge init'"
237
+ issues=$((issues + 1))
238
+ fi
239
+
240
+ if [[ -f "$local_config_file" ]]; then
241
+ log_success "config.local.json: found (local overrides active)"
242
+ fi
243
+
244
+ # --- agents.json sync check ---
245
+ local agents_file="$FORGE_ROOT/config/agents.json"
246
+ if [[ -f "$agents_file" ]]; then
247
+ log_success "agents.json: found"
248
+ local agent_count
249
+ agent_count=$(node -e "const a=require('$agents_file'); console.log(Object.keys(a.agents||{}).length)" 2>/dev/null || echo "?")
250
+ echo " Agents defined: $agent_count"
251
+ else
252
+ log_error "agents.json: not found"
253
+ issues=$((issues + 1))
254
+ fi
255
+
256
+ # --- Alias collision check ---
257
+ if [[ -f "$agents_file" ]]; then
258
+ local collisions
259
+ collisions=$(node -e "
260
+ const a = require('$agents_file').agents || {};
261
+ const seen = {};
262
+ const dupes = [];
263
+ for (const [name, info] of Object.entries(a)) {
264
+ const aliases = [name, ...(info.aliases || [])];
265
+ for (const alias of aliases) {
266
+ if (seen[alias] && seen[alias] !== name) {
267
+ dupes.push(alias + ' (' + seen[alias] + ' vs ' + name + ')');
268
+ }
269
+ seen[alias] = name;
270
+ }
271
+ }
272
+ if (dupes.length) console.log('COLLISION: ' + dupes.join(', '));
273
+ " 2>/dev/null || true)
274
+ if [[ -n "$collisions" ]]; then
275
+ log_error "Alias collision detected: $collisions"
276
+ issues=$((issues + 1))
277
+ else
278
+ log_success "No alias collisions"
279
+ fi
280
+ fi
281
+
282
+ # --- Task directories ---
283
+ echo ""
284
+ echo "Task System"
285
+ echo "────────────────────────────────────────────────"
286
+
287
+ local tasks_dir="$FORGE_ROOT/tasks"
288
+ local required_dirs=("pending" "in-progress" "completed" "review" "approved" "merged" "needs-changes" "attention" "bugs")
289
+ for dir in "${required_dirs[@]}"; do
290
+ if [[ -d "$tasks_dir/$dir" ]]; then
291
+ local count
292
+ count=$(ls "$tasks_dir/$dir/"*.md 2>/dev/null | wc -l | tr -d ' ')
293
+ log_success "$dir/: $count task(s)"
294
+ else
295
+ log_warn "$dir/: missing (will be created by daemon)"
296
+ fi
297
+ done
298
+
299
+ # --- Daemon ---
300
+ echo ""
301
+ echo "Daemon"
302
+ echo "────────────────────────────────────────────────"
303
+
304
+ local pid_file="$FORGE_ROOT/.forge/daemon.pid"
305
+ if [[ -f "$pid_file" ]]; then
306
+ local pid
307
+ pid=$(cat "$pid_file" 2>/dev/null)
308
+ if kill -0 "$pid" 2>/dev/null; then
309
+ log_success "Daemon running (PID $pid)"
310
+ else
311
+ log_warn "Daemon PID file exists but process is dead — run 'forge daemon start'"
312
+ fi
313
+ else
314
+ log_warn "Daemon not running — run 'forge daemon start' for automated task routing"
315
+ fi
316
+
317
+ # --- Claude Code test ---
318
+ echo ""
319
+ echo "Claude Code Integration"
320
+ echo "────────────────────────────────────────────────"
321
+
322
+ local test_output
323
+ test_output=$(claude --system-prompt "You are a test. Reply only: FORGE_TEST_OK" --print "test" 2>&1 | head -1)
324
+ if [[ "$test_output" == *"FORGE_TEST_OK"* || "$test_output" == *"test"* ]]; then
325
+ log_success "Personality injection: working"
326
+ else
327
+ log_warn "Personality injection: response unexpected"
328
+ echo " Output: $test_output"
329
+ fi
330
+
331
+ # --- Summary ---
332
+ echo ""
333
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
334
+ if [[ $issues -eq 0 ]]; then
335
+ log_success "🔥 Forge looks healthy"
336
+ else
337
+ log_error "Found $issues issue(s) — resolve the errors above before starting"
338
+ fi
339
+ echo ""
340
+ }
341
+
342
+ cmd_spawn() {
343
+ local agent="${1:-}"
344
+
345
+ if [[ -z "$agent" ]]; then
346
+ log_error "No agent specified."
347
+ echo "Usage: forge spawn <agent>"
348
+ echo ""
349
+ show_available_agents
350
+ exit $EXIT_INVALID_ARGUMENT
351
+ fi
352
+
353
+ # Validate agent before passing to spawn script (SECURITY: whitelist check)
354
+ if ! is_valid_agent "$agent"; then
355
+ log_error "Unknown agent: $agent"
356
+ echo ""
357
+ show_available_agents
358
+ exit $EXIT_INVALID_ARGUMENT
359
+ fi
360
+
361
+ "$SCRIPT_DIR/forge-spawn.sh" "$agent"
362
+ }
363
+
364
+ cmd_daemon() {
365
+ local action="${1:-status}"
366
+
367
+ case "$action" in
368
+ "start")
369
+ echo "Starting forge daemon..."
370
+ "$SCRIPT_DIR/forge-daemon.sh" start
371
+ ;;
372
+ "stop")
373
+ echo "Stopping forge daemon..."
374
+ "$SCRIPT_DIR/forge-daemon.sh" stop
375
+ ;;
376
+ "status")
377
+ "$SCRIPT_DIR/forge-daemon.sh" status
378
+ ;;
379
+ "notifications"|"notify")
380
+ shift
381
+ "$SCRIPT_DIR/forge-daemon.sh" notifications "$@"
382
+ ;;
383
+ "clear")
384
+ "$SCRIPT_DIR/forge-daemon.sh" clear
385
+ ;;
386
+ *)
387
+ echo "Usage: forge daemon [start|stop|status|notifications|clear]"
388
+ ;;
389
+ esac
390
+ }
391
+
392
+ cmd_config() {
393
+ local setting="${1:-}"
394
+ local value="${2:-}"
395
+ local config_file="$FORGE_ROOT/.forge/config.json"
396
+
397
+ if [[ ! -f "$config_file" ]]; then
398
+ log_error "Forge not initialized. Run 'forge init' first."
399
+ exit $EXIT_CONFIG_ERROR
400
+ fi
401
+
402
+ case "$setting" in
403
+ "worker-loop"|"loop")
404
+ case "$value" in
405
+ "on"|"true"|"1"|"enable"|"enabled")
406
+ # Enable worker loop
407
+ json_write_bool "$config_file" "worker_loop_enabled" true
408
+ log_success "Worker Loop enabled"
409
+ echo "Workers will now keep running to check for new tasks."
410
+ ;;
411
+ "off"|"false"|"0"|"disable"|"disabled")
412
+ # Disable worker loop
413
+ json_write_bool "$config_file" "worker_loop_enabled" false
414
+ log_success "Worker Loop disabled"
415
+ echo "Workers will exit after completing their tasks."
416
+ ;;
417
+ ""|"status")
418
+ # Show current status
419
+ local current
420
+ current=$(json_read "$config_file" "worker_loop_enabled" "false")
421
+ if [[ "$current" == "true" ]]; then
422
+ echo "Worker Loop: enabled"
423
+ else
424
+ echo "Worker Loop: disabled"
425
+ fi
426
+ ;;
427
+ *)
428
+ echo "Usage: forge config worker-loop [on|off|status]"
429
+ ;;
430
+ esac
431
+ ;;
432
+ "")
433
+ # Show all config
434
+ echo ""
435
+ log_header "Forge Configuration"
436
+ echo ""
437
+ json_pretty "$config_file"
438
+ echo ""
439
+ ;;
440
+ *)
441
+ log_error "Unknown setting: $setting"
442
+ echo ""
443
+ echo "Available settings:"
444
+ echo " worker-loop Toggle persistent worker mode (on/off)"
445
+ echo ""
446
+ echo "Usage: forge config <setting> [value]"
447
+ ;;
448
+ esac
449
+ }
450
+
451
+ cmd_help() {
452
+ echo ""
453
+ log_header "🔥 Vibe Forge"
454
+ echo ""
455
+ echo "Usage: forge [command] [options]"
456
+ echo ""
457
+ echo "Commands:"
458
+ echo " (none) Start the Planning Hub (main session)"
459
+ echo " init Initialize Vibe Forge for this project"
460
+ echo " start <agent> Start a specific worker agent (in current terminal)"
461
+ echo " spawn <agent> Spawn agent in new terminal window/tab"
462
+ echo " status Show current forge status"
463
+ echo " doctor Full environment diagnostics (alias: test)"
464
+ echo " daemon <action> Manage background daemon (start|stop|status|notifications|clear)"
465
+ echo " config <setting> View or change configuration settings"
466
+ echo " help Show this help message"
467
+ echo ""
468
+ show_available_agents
469
+ echo ""
470
+ echo "Configuration:"
471
+ echo " forge config Show all settings"
472
+ echo " forge config worker-loop on Enable persistent worker mode"
473
+ echo " forge config worker-loop off Disable persistent worker mode"
474
+ echo ""
475
+ echo "Examples:"
476
+ echo " forge Start Planning Hub"
477
+ echo " forge init Initialize for new project"
478
+ echo " forge start anvil Start Anvil (frontend) agent"
479
+ echo " forge spawn fe Spawn frontend agent in new terminal"
480
+ echo " forge status Check current status"
481
+ echo ""
482
+ }
483
+
484
+ # =============================================================================
485
+ # Main
486
+ # =============================================================================
487
+
488
+ main() {
489
+ local command="${1:-}"
490
+
491
+ case "$command" in
492
+ "init")
493
+ shift
494
+ cmd_init "$@"
495
+ ;;
496
+ "start")
497
+ shift
498
+ cmd_start "$@"
499
+ ;;
500
+ "spawn")
501
+ shift
502
+ cmd_spawn "$@"
503
+ ;;
504
+ "status")
505
+ cmd_status
506
+ ;;
507
+ "test"|"doctor")
508
+ cmd_doctor
509
+ ;;
510
+ "daemon")
511
+ shift
512
+ cmd_daemon "$@"
513
+ ;;
514
+ "config")
515
+ shift
516
+ cmd_config "$@"
517
+ ;;
518
+ "help"|"--help"|"-h")
519
+ cmd_help
520
+ ;;
521
+ "")
522
+ # Default: start Planning Hub
523
+ cmd_start "hub"
524
+ ;;
525
+ *)
526
+ log_error "Unknown command: $command"
527
+ echo "Run 'forge help' for usage."
528
+ exit $EXIT_INVALID_ARGUMENT
529
+ ;;
530
+ esac
531
+ }
532
+
533
+ main "$@"
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Vibe Forge - Agent Resolution and Validation
4
+ # Source this file in other scripts: source "$SCRIPT_DIR/lib/agents.sh"
5
+ #
6
+ # SECURITY: This module provides safe agent name resolution with whitelist validation.
7
+ # Never pass user input directly to shell commands - always resolve through these functions.
8
+ #
9
+
10
+ # Ensure constants are loaded
11
+ if [[ -z "${VALID_AGENTS+x}" ]]; then
12
+ echo "Error: constants.sh must be sourced before agents.sh" >&2
13
+ exit 1
14
+ fi
15
+
16
+ # resolve_agent ALIAS
17
+ # Converts an agent alias to its canonical name.
18
+ # Returns: canonical name on stdout, empty string if not found
19
+ # Exit code: 0 if found, 1 if not found
20
+ #
21
+ # SECURITY: This function validates against a whitelist.
22
+ # The returned value is safe to use in file paths and commands.
23
+ resolve_agent() {
24
+ local input="$1"
25
+
26
+ # Normalize to lowercase for matching
27
+ local normalized
28
+ normalized=$(echo "$input" | tr '[:upper:]' '[:lower:]')
29
+
30
+ # Look up in alias map
31
+ local canonical="${AGENT_ALIASES[$normalized]:-}"
32
+
33
+ if [[ -n "$canonical" ]]; then
34
+ echo "$canonical"
35
+ return 0
36
+ fi
37
+
38
+ return 1
39
+ }
40
+
41
+ # is_valid_agent AGENT
42
+ # Checks if an agent name is valid (either canonical or alias)
43
+ # Returns: 0 if valid, 1 if invalid
44
+ is_valid_agent() {
45
+ local agent="$1"
46
+ resolve_agent "$agent" >/dev/null 2>&1
47
+ }
48
+
49
+ # get_agent_personality_path FORGE_ROOT AGENT
50
+ # Returns the path to an agent's personality file.
51
+ # Uses AGENT_PERSONALITY_FILES from constants.sh (or loaded from agents.json)
52
+ # SECURITY: Validates agent name before constructing path.
53
+ # Returns: full path on stdout
54
+ # Exit code: 0 on success, 1 if agent invalid or file missing
55
+ get_agent_personality_path() {
56
+ local forge_root="$1"
57
+ local agent="$2"
58
+
59
+ # Resolve and validate agent
60
+ local canonical
61
+ canonical=$(resolve_agent "$agent") || {
62
+ return 1
63
+ }
64
+
65
+ # Get personality file path from configuration
66
+ local relative_path="${AGENT_PERSONALITY_FILES[$canonical]:-}"
67
+ if [[ -z "$relative_path" ]]; then
68
+ # Fallback: construct path if not in config
69
+ relative_path="agents/$canonical/personality.md"
70
+ fi
71
+
72
+ local personality_path="$forge_root/$relative_path"
73
+
74
+ # Validate file exists
75
+ if [[ ! -f "$personality_path" ]]; then
76
+ return 1
77
+ fi
78
+
79
+ # SECURITY: Verify the resolved path is within agents directory
80
+ local real_path
81
+ real_path=$(cd "$(dirname "$personality_path")" 2>/dev/null && pwd)/$(basename "$personality_path")
82
+ local agents_dir
83
+ agents_dir=$(cd "$forge_root/agents" 2>/dev/null && pwd)
84
+
85
+ if [[ "$real_path" != "$agents_dir"/* ]]; then
86
+ echo "Security error: Path traversal detected" >&2
87
+ return 1
88
+ fi
89
+
90
+ echo "$personality_path"
91
+ return 0
92
+ }
93
+
94
+ # show_available_agents
95
+ # Prints a formatted list of available agents with aliases
96
+ # Uses data from AGENT_DISPLAY_NAMES, AGENT_ROLES, and AGENT_ALIASES
97
+ show_available_agents() {
98
+ echo "Available agents:"
99
+
100
+ # Iterate over valid agents (excluding hub for user display)
101
+ for agent in "${VALID_AGENTS[@]}"; do
102
+ if [[ "$agent" == "hub" ]]; then
103
+ continue
104
+ fi
105
+
106
+ # Get display info
107
+ local role="${AGENT_ROLES[$agent]:-}"
108
+
109
+ # Collect aliases for this agent
110
+ local aliases=()
111
+ for alias in "${!AGENT_ALIASES[@]}"; do
112
+ if [[ "${AGENT_ALIASES[$alias]}" == "$agent" && "$alias" != "$agent" ]]; then
113
+ aliases+=("$alias")
114
+ fi
115
+ done
116
+
117
+ # Format aliases (take first 3)
118
+ local alias_str=""
119
+ if [[ ${#aliases[@]} -gt 0 ]]; then
120
+ # Sort and take first 3
121
+ IFS=$'\n' sorted=($(printf '%s\n' "${aliases[@]}" | sort)); unset IFS
122
+ local display_aliases=("${sorted[@]:0:3}")
123
+ alias_str="($(IFS=", "; echo "${display_aliases[*]}"))"
124
+ fi
125
+
126
+ # Print formatted line
127
+ printf " %-9s %-25s - %s\n" "$agent" "$alias_str" "$role"
128
+ done
129
+ }
130
+
131
+ # get_agent_display_name AGENT
132
+ # Returns the display name for an agent (e.g., "Anvil" for "anvil")
133
+ # Uses AGENT_DISPLAY_NAMES from constants.sh (or loaded from agents.json)
134
+ get_agent_display_name() {
135
+ local agent="$1"
136
+ local canonical
137
+ canonical=$(resolve_agent "$agent") || return 1
138
+
139
+ # Look up in AGENT_DISPLAY_NAMES array
140
+ local display_name="${AGENT_DISPLAY_NAMES[$canonical]:-}"
141
+ if [[ -n "$display_name" ]]; then
142
+ echo "$display_name"
143
+ else
144
+ # Fallback to canonical name with first letter capitalized
145
+ echo "${canonical^}"
146
+ fi
147
+ }
148
+
149
+ # get_agent_role AGENT
150
+ # Returns the role description for an agent
151
+ get_agent_role() {
152
+ local agent="$1"
153
+ local canonical
154
+ canonical=$(resolve_agent "$agent") || return 1
155
+
156
+ echo "${AGENT_ROLES[$canonical]:-}"
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
+ }