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
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Agents API - List and query agent status
3
+ *
4
+ * Reads agent status from:
5
+ * 1. SQLite database (if available) - daemon's aggregated view
6
+ * 2. JSON files in context/agent-status/ - direct file read fallback
7
+ *
8
+ * Endpoints:
9
+ * GET /api/agents - List all agents with their status
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ // Agent status directory
16
+ const AGENT_STATUS_DIR = 'context/agent-status';
17
+
18
+ // Known agents (for complete listing even if status file missing)
19
+ const KNOWN_AGENTS = [
20
+ 'planning-hub',
21
+ 'architect',
22
+ 'furnace',
23
+ 'anvil',
24
+ 'crucible',
25
+ 'temper',
26
+ 'ember',
27
+ 'scribe',
28
+ 'aegis',
29
+ 'pixel',
30
+ 'oracle',
31
+ 'loki'
32
+ ];
33
+
34
+ // Status display order
35
+ const STATUS_ORDER = {
36
+ 'working': 0,
37
+ 'testing': 1,
38
+ 'blocked': 2,
39
+ 'attention': 3,
40
+ 'idle': 4,
41
+ 'unknown': 5,
42
+ 'offline': 6
43
+ };
44
+
45
+ /**
46
+ * Try to read agent status from SQLite database
47
+ * @param {string} projectRoot - Project root directory
48
+ * @returns {Array|null} Agent statuses or null if DB not available
49
+ */
50
+ async function readFromDatabase(projectRoot) {
51
+ try {
52
+ // Check for better-sqlite3 (synchronous)
53
+ const Database = require('better-sqlite3');
54
+ const dbPath = path.join(projectRoot, '.forge', 'forge.db');
55
+
56
+ if (!fs.existsSync(dbPath)) {
57
+ return null;
58
+ }
59
+
60
+ const db = new Database(dbPath, { readonly: true });
61
+
62
+ try {
63
+ const rows = db.prepare(`
64
+ SELECT agent, status, task, message, updated_at
65
+ FROM agent_status
66
+ ORDER BY agent
67
+ `).all();
68
+
69
+ return rows.map(row => ({
70
+ agent: row.agent,
71
+ status: row.status || 'unknown',
72
+ task: row.task || null,
73
+ message: row.message || null,
74
+ updatedAt: row.updated_at || null,
75
+ source: 'database'
76
+ }));
77
+ } finally {
78
+ db.close();
79
+ }
80
+ } catch (err) {
81
+ // better-sqlite3 not available or DB error, fall back to files
82
+ return null;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Read agent status from JSON file
88
+ * @param {string} filePath - Path to status file
89
+ * @returns {Object|null} Agent status or null
90
+ */
91
+ function readStatusFile(filePath) {
92
+ try {
93
+ const content = fs.readFileSync(filePath, 'utf8');
94
+ const data = JSON.parse(content);
95
+ const stats = fs.statSync(filePath);
96
+
97
+ return {
98
+ agent: data.agent || path.basename(filePath, '.json'),
99
+ status: data.status || 'unknown',
100
+ task: data.task || null,
101
+ message: data.message || null,
102
+ updatedAt: data.updated || stats.mtime.toISOString(),
103
+ source: 'file'
104
+ };
105
+ } catch (err) {
106
+ console.error(`[Agents] Error reading ${filePath}: ${err.message}`);
107
+ return null;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Read all agent statuses from files
113
+ * @param {string} projectRoot - Project root directory
114
+ * @returns {Array} Agent statuses
115
+ */
116
+ function readFromFiles(projectRoot) {
117
+ const statusDir = path.join(projectRoot, AGENT_STATUS_DIR);
118
+ const agents = [];
119
+
120
+ if (!fs.existsSync(statusDir)) {
121
+ return agents;
122
+ }
123
+
124
+ const files = fs.readdirSync(statusDir).filter(f => f.endsWith('.json'));
125
+
126
+ for (const file of files) {
127
+ const filePath = path.join(statusDir, file);
128
+ const status = readStatusFile(filePath);
129
+
130
+ if (status) {
131
+ agents.push(status);
132
+ }
133
+ }
134
+
135
+ return agents;
136
+ }
137
+
138
+ /**
139
+ * Get agent metadata (icon, role) from personality files
140
+ * @param {string} projectRoot - Project root directory
141
+ * @param {string} agentName - Agent name
142
+ * @returns {Object} Agent metadata
143
+ */
144
+ function getAgentMetadata(projectRoot, agentName) {
145
+ const personalityPath = path.join(projectRoot, 'agents', agentName, 'personality.md');
146
+
147
+ const defaults = {
148
+ icon: getDefaultIcon(agentName),
149
+ role: 'Agent',
150
+ color: getDefaultColor(agentName)
151
+ };
152
+
153
+ if (!fs.existsSync(personalityPath)) {
154
+ return defaults;
155
+ }
156
+
157
+ try {
158
+ const content = fs.readFileSync(personalityPath, 'utf8');
159
+
160
+ // Extract icon from first line or **Icon:** field
161
+ const iconMatch = content.match(/\*\*Icon:\*\*\s*(.+)/);
162
+ if (iconMatch) {
163
+ defaults.icon = iconMatch[1].trim();
164
+ }
165
+
166
+ // Extract role from **Role:** field
167
+ const roleMatch = content.match(/\*\*Role:\*\*\s*(.+)/);
168
+ if (roleMatch) {
169
+ defaults.role = roleMatch[1].trim();
170
+ }
171
+
172
+ return defaults;
173
+ } catch (err) {
174
+ return defaults;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Get default icon for agent
180
+ * @param {string} agentName - Agent name
181
+ * @returns {string} Icon emoji
182
+ */
183
+ function getDefaultIcon(agentName) {
184
+ const icons = {
185
+ 'planning-hub': '⚙️',
186
+ 'architect': '🏛️',
187
+ 'furnace': '🔥',
188
+ 'anvil': '🔨',
189
+ 'crucible': '🧪',
190
+ 'temper': '⚖️',
191
+ 'ember': '✨',
192
+ 'scribe': '📝',
193
+ 'aegis': '🔒',
194
+ 'pixel': '🎨',
195
+ 'oracle': '🔮',
196
+ 'loki': '🎭'
197
+ };
198
+ return icons[agentName] || '🤖';
199
+ }
200
+
201
+ /**
202
+ * Get default color for agent (for UI)
203
+ * @param {string} agentName - Agent name
204
+ * @returns {string} Color code
205
+ */
206
+ function getDefaultColor(agentName) {
207
+ const colors = {
208
+ 'planning-hub': '#6366f1',
209
+ 'architect': '#8b5cf6',
210
+ 'furnace': '#ef4444',
211
+ 'anvil': '#f97316',
212
+ 'crucible': '#22c55e',
213
+ 'temper': '#8b5cf6',
214
+ 'ember': '#eab308',
215
+ 'scribe': '#06b6d4',
216
+ 'aegis': '#14b8a6',
217
+ 'pixel': '#ec4899',
218
+ 'oracle': '#FBBF24',
219
+ 'loki': '#7C3AED'
220
+ };
221
+ return colors[agentName] || '#6b7280';
222
+ }
223
+
224
+ /**
225
+ * List all agents with their status
226
+ * @param {string} projectRoot - Project root directory
227
+ * @returns {Object} Agents listing with summary
228
+ */
229
+ async function listAgents(projectRoot) {
230
+ // Try database first, then fall back to files
231
+ let statuses = await readFromDatabase(projectRoot);
232
+
233
+ if (!statuses) {
234
+ statuses = readFromFiles(projectRoot);
235
+ }
236
+
237
+ // Create map for quick lookup
238
+ const statusMap = new Map();
239
+ for (const status of statuses) {
240
+ statusMap.set(status.agent, status);
241
+ }
242
+
243
+ // Build complete agent list
244
+ const agents = [];
245
+
246
+ for (const agentName of KNOWN_AGENTS) {
247
+ const status = statusMap.get(agentName);
248
+ const metadata = getAgentMetadata(projectRoot, agentName);
249
+
250
+ agents.push({
251
+ agent: agentName,
252
+ displayName: agentName.split('-').map(w =>
253
+ w.charAt(0).toUpperCase() + w.slice(1)
254
+ ).join(' '),
255
+ icon: metadata.icon,
256
+ role: metadata.role,
257
+ color: metadata.color,
258
+ status: status?.status || 'offline',
259
+ task: status?.task || null,
260
+ message: status?.message || null,
261
+ updatedAt: status?.updatedAt || null,
262
+ isOnline: status != null
263
+ });
264
+ }
265
+
266
+ // Add any unknown agents found in status files
267
+ for (const status of statuses) {
268
+ if (!KNOWN_AGENTS.includes(status.agent)) {
269
+ const metadata = getAgentMetadata(projectRoot, status.agent);
270
+ agents.push({
271
+ agent: status.agent,
272
+ displayName: status.agent.split('-').map(w =>
273
+ w.charAt(0).toUpperCase() + w.slice(1)
274
+ ).join(' '),
275
+ icon: metadata.icon,
276
+ role: metadata.role,
277
+ color: metadata.color,
278
+ status: status.status,
279
+ task: status.task,
280
+ message: status.message,
281
+ updatedAt: status.updatedAt,
282
+ isOnline: true
283
+ });
284
+ }
285
+ }
286
+
287
+ // Sort by status priority, then by name
288
+ agents.sort((a, b) => {
289
+ const sa = STATUS_ORDER[a.status] ?? 5;
290
+ const sb = STATUS_ORDER[b.status] ?? 5;
291
+ if (sa !== sb) return sa - sb;
292
+ return a.agent.localeCompare(b.agent);
293
+ });
294
+
295
+ // Build summary
296
+ const summary = {
297
+ total: agents.length,
298
+ online: agents.filter(a => a.isOnline).length,
299
+ working: agents.filter(a => a.status === 'working').length,
300
+ idle: agents.filter(a => a.status === 'idle').length,
301
+ blocked: agents.filter(a => a.status === 'blocked').length,
302
+ attention: agents.filter(a => a.status === 'attention').length
303
+ };
304
+
305
+ return {
306
+ agents,
307
+ summary,
308
+ dataSource: statuses.length > 0 && statuses[0].source || 'none'
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Get single agent details
314
+ * @param {string} projectRoot - Project root directory
315
+ * @param {string} agentName - Agent name
316
+ * @returns {Object|null} Agent details or null
317
+ */
318
+ async function getAgent(projectRoot, agentName) {
319
+ // Sanitize agent name
320
+ const safeName = agentName.toLowerCase().replace(/[^a-z0-9-]/g, '');
321
+
322
+ const result = await listAgents(projectRoot);
323
+ return result.agents.find(a => a.agent === safeName) || null;
324
+ }
325
+
326
+ module.exports = {
327
+ listAgents,
328
+ getAgent,
329
+ readFromFiles,
330
+ readFromDatabase,
331
+ getAgentMetadata,
332
+ KNOWN_AGENTS
333
+ };