@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
@@ -0,0 +1,483 @@
1
+ /**
2
+ * Dispatch API - Create task files for agent execution
3
+ *
4
+ * This is the key feature that transforms the dashboard from a viewer
5
+ * into a command center. Users can click dispatch buttons to create
6
+ * task files that agents will pick up.
7
+ *
8
+ * Endpoint:
9
+ * POST /api/dispatch - Create and dispatch a task
10
+ *
11
+ * Request body:
12
+ * {
13
+ * "type": "stale-docs", // Issue type
14
+ * "target": "README.md", // Target file/resource
15
+ * "agent": "scribe", // Suggested agent
16
+ * "context": { ... } // Additional context
17
+ * }
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const tasksApi = require('./tasks');
23
+
24
+ // Dispatch type configurations
25
+ const DISPATCH_TYPES = {
26
+ 'stale-docs': {
27
+ prefix: 'DOC',
28
+ priority: 'medium',
29
+ suggestedAgent: 'scribe',
30
+ titleTemplate: (target) => `Update stale documentation: ${target}`,
31
+ descriptionTemplate: (target, context) =>
32
+ `The file "${target}" has not been updated in ${context.daysSinceUpdate || 'many'} days and may contain outdated information.`,
33
+ acceptanceCriteria: [
34
+ 'Documentation reviewed and updated',
35
+ 'All code references are accurate',
36
+ 'Examples are current and working'
37
+ ]
38
+ },
39
+ 'failing-test': {
40
+ prefix: 'TEST',
41
+ priority: 'high',
42
+ suggestedAgent: 'crucible',
43
+ titleTemplate: (target) => `Fix failing test: ${target}`,
44
+ descriptionTemplate: (target, context) =>
45
+ `Test file "${target}" has ${context.failureCount || 'some'} failing test(s). Error: ${context.error || 'Unknown'}`,
46
+ acceptanceCriteria: [
47
+ 'All tests passing',
48
+ 'No test skips introduced',
49
+ 'Root cause addressed'
50
+ ]
51
+ },
52
+ 'security-issue': {
53
+ prefix: 'SEC',
54
+ priority: 'critical',
55
+ suggestedAgent: 'aegis',
56
+ titleTemplate: (target) => `Security issue: ${target}`,
57
+ descriptionTemplate: (target, context) =>
58
+ `Security vulnerability detected: ${context.vulnerability || target}. Severity: ${context.severity || 'unknown'}`,
59
+ acceptanceCriteria: [
60
+ 'Vulnerability patched',
61
+ 'No regressions introduced',
62
+ 'Security tests added if applicable'
63
+ ]
64
+ },
65
+ 'low-coverage': {
66
+ prefix: 'COV',
67
+ priority: 'medium',
68
+ suggestedAgent: 'crucible',
69
+ titleTemplate: (target) => `Improve test coverage: ${target}`,
70
+ descriptionTemplate: (target, context) =>
71
+ `File "${target}" has low test coverage (${context.coverage || '<50'}%). Add tests to improve coverage.`,
72
+ acceptanceCriteria: [
73
+ 'Coverage improved to acceptable threshold',
74
+ 'Critical paths covered',
75
+ 'Edge cases tested'
76
+ ]
77
+ },
78
+ 'todo-fixme': {
79
+ prefix: 'TODO',
80
+ priority: 'low',
81
+ suggestedAgent: null, // Auto-assign based on file type
82
+ titleTemplate: (target) => `Address TODO/FIXME: ${target}`,
83
+ descriptionTemplate: (target, context) =>
84
+ `TODO/FIXME marker found in "${target}": ${context.marker || 'Review and address'}`,
85
+ acceptanceCriteria: [
86
+ 'TODO/FIXME addressed or documented',
87
+ 'Code improved as needed',
88
+ 'Marker removed if resolved'
89
+ ]
90
+ },
91
+ 'pending-review': {
92
+ prefix: 'REV',
93
+ priority: 'high',
94
+ suggestedAgent: 'temper',
95
+ titleTemplate: (target) => `Review pending: ${target}`,
96
+ descriptionTemplate: (target, context) =>
97
+ `PR #${context.prNumber || target} has been waiting for review for ${context.hoursWaiting || 'many'} hours.`,
98
+ acceptanceCriteria: [
99
+ 'PR reviewed',
100
+ 'Feedback provided',
101
+ 'Approval or changes requested'
102
+ ]
103
+ },
104
+ 'custom': {
105
+ prefix: 'TASK',
106
+ priority: 'medium',
107
+ suggestedAgent: null,
108
+ titleTemplate: (target) => target, // Use target as title
109
+ descriptionTemplate: (target, context) =>
110
+ context.description || 'Custom task dispatched from dashboard.',
111
+ acceptanceCriteria: [
112
+ 'Task completed as specified'
113
+ ]
114
+ }
115
+ };
116
+
117
+ /**
118
+ * Generate unique dispatch task ID
119
+ * @param {string} prefix - ID prefix
120
+ * @returns {string} Task ID
121
+ */
122
+ function generateDispatchId(prefix) {
123
+ const now = new Date();
124
+ const seq = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
125
+ return `${prefix}-${now.toISOString().slice(0, 10).replace(/-/g, '')}-${seq}`;
126
+ }
127
+
128
+ /**
129
+ * Determine agent based on file type
130
+ * @param {string} target - Target file path
131
+ * @returns {string|null} Suggested agent
132
+ */
133
+ function suggestAgentForFile(target) {
134
+ if (!target) return null;
135
+
136
+ const ext = path.extname(target).toLowerCase();
137
+ const dir = path.dirname(target).toLowerCase();
138
+
139
+ // Backend files
140
+ if (dir.includes('/api/') || dir.includes('/services/') ||
141
+ dir.includes('/models/') || dir.includes('/middleware/')) {
142
+ return 'furnace';
143
+ }
144
+
145
+ // Frontend files
146
+ if (dir.includes('/components/') || dir.includes('/pages/') ||
147
+ dir.includes('/ui/') || ext === '.tsx' || ext === '.jsx') {
148
+ return 'anvil';
149
+ }
150
+
151
+ // Test files
152
+ if (target.includes('.test.') || target.includes('.spec.') ||
153
+ dir.includes('/test')) {
154
+ return 'crucible';
155
+ }
156
+
157
+ // Documentation
158
+ if (ext === '.md' || dir.includes('/docs/')) {
159
+ return 'scribe';
160
+ }
161
+
162
+ // Security-related
163
+ if (target.includes('auth') || target.includes('security') ||
164
+ target.includes('permission')) {
165
+ return 'aegis';
166
+ }
167
+
168
+ // Style files
169
+ if (ext === '.css' || ext === '.scss' || ext === '.less') {
170
+ return 'pixel';
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ const MAX_TARGET_LENGTH = 500
177
+ const MAX_TITLE_LENGTH = 200
178
+ const MAX_CONTEXT_STRING_LENGTH = 10_000
179
+ const AGENT_ID_PATTERN = /^[a-z0-9_-]+$/
180
+
181
+ /**
182
+ * Validate dispatch request
183
+ * @param {Object} data - Request data
184
+ * @returns {Object} Validation result
185
+ */
186
+ function validateDispatchRequest(data) {
187
+ const errors = [];
188
+
189
+ if (!data.type) {
190
+ errors.push('type is required');
191
+ } else if (!DISPATCH_TYPES[data.type]) {
192
+ errors.push(`unknown type: ${data.type}. Valid types: ${Object.keys(DISPATCH_TYPES).join(', ')}`);
193
+ }
194
+
195
+ if (!data.target && data.type !== 'custom') {
196
+ errors.push('target is required');
197
+ } else if (data.target) {
198
+ if (typeof data.target !== 'string') {
199
+ errors.push('target must be a string');
200
+ } else if (data.target.length > MAX_TARGET_LENGTH) {
201
+ errors.push(`target too long (max ${MAX_TARGET_LENGTH} chars)`);
202
+ }
203
+ }
204
+
205
+ if (data.title && data.title.length > MAX_TITLE_LENGTH) {
206
+ errors.push(`title too long (max ${MAX_TITLE_LENGTH} chars)`);
207
+ }
208
+
209
+ if (data.agent) {
210
+ if (typeof data.agent !== 'string') {
211
+ errors.push('agent must be a string');
212
+ } else if (!AGENT_ID_PATTERN.test(data.agent)) {
213
+ errors.push('agent must contain only lowercase letters, numbers, underscores, and hyphens');
214
+ }
215
+ }
216
+
217
+ if (data.context) {
218
+ if (typeof data.context !== 'object' || Array.isArray(data.context)) {
219
+ errors.push('context must be an object');
220
+ } else {
221
+ // Limit total serialized context size to prevent oversized payloads
222
+ const contextSize = JSON.stringify(data.context).length;
223
+ if (contextSize > MAX_CONTEXT_STRING_LENGTH) {
224
+ errors.push(`context too large (${contextSize} chars serialized, max ${MAX_CONTEXT_STRING_LENGTH})`);
225
+ }
226
+ }
227
+ }
228
+
229
+ return {
230
+ valid: errors.length === 0,
231
+ errors
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Dispatch a task
237
+ * @param {string} projectRoot - Project root directory
238
+ * @param {Object} data - Dispatch request data
239
+ * @param {Function} broadcast - WebSocket broadcast function
240
+ * @returns {Object} Created task
241
+ */
242
+ // Sentinel error type so the server can distinguish validation (400) from runtime (500)
243
+ class DispatchValidationError extends Error {
244
+ constructor(errors) {
245
+ super(`Invalid dispatch request: ${errors.join(', ')}`)
246
+ this.name = 'DispatchValidationError'
247
+ this.errors = errors
248
+ this.statusCode = 400
249
+ }
250
+ }
251
+
252
+ async function dispatch(projectRoot, data, broadcast) {
253
+ // Validate request — throws DispatchValidationError (400) on failure
254
+ const validation = validateDispatchRequest(data);
255
+ if (!validation.valid) {
256
+ throw new DispatchValidationError(validation.errors);
257
+ }
258
+
259
+ const dispatchType = DISPATCH_TYPES[data.type];
260
+
261
+ // Sanitize context strings: strip any markdown frontmatter (prevents injecting fake task metadata)
262
+ const rawContext = data.context || {};
263
+ const context = Object.fromEntries(
264
+ Object.entries(rawContext).map(([k, v]) => [
265
+ k,
266
+ typeof v === 'string' ? v.replace(/^---[\s\S]*?---\n?/, '').trim() : v
267
+ ])
268
+ );
269
+
270
+ // Determine agent
271
+ let agent = data.agent || dispatchType.suggestedAgent;
272
+ if (!agent) {
273
+ agent = suggestAgentForFile(data.target);
274
+ }
275
+
276
+ // Generate task ID
277
+ const taskId = generateDispatchId(dispatchType.prefix);
278
+
279
+ // Build task title and description
280
+ const title = data.title || dispatchType.titleTemplate(data.target);
281
+ const description = dispatchType.descriptionTemplate(data.target, context);
282
+
283
+ // Build acceptance criteria
284
+ const criteria = data.acceptanceCriteria || dispatchType.acceptanceCriteria;
285
+
286
+ // Create task content
287
+ const taskData = {
288
+ id: taskId,
289
+ title: title,
290
+ type: mapDispatchTypeToTaskType(data.type),
291
+ priority: data.priority || dispatchType.priority,
292
+ assignedTo: agent,
293
+ createdBy: 'dashboard-dispatch',
294
+ summary: description,
295
+ context: buildContextSection(data, context)
296
+ };
297
+
298
+ // Build full task file content
299
+ const tasksDir = path.join(projectRoot, 'tasks', 'pending');
300
+ const slug = title
301
+ .toLowerCase()
302
+ .replace(/[^a-z0-9]+/g, '-')
303
+ .replace(/^-|-$/g, '')
304
+ .substring(0, 50);
305
+
306
+ const filename = `${taskId}-${slug}.md`;
307
+ const filePath = path.join(tasksDir, filename);
308
+
309
+ const now = new Date().toISOString();
310
+ const content = `---
311
+ id: ${taskId}
312
+ title: "${title.replace(/"/g, '\\"')}"
313
+ type: ${taskData.type}
314
+ priority: ${taskData.priority}
315
+ status: pending
316
+ created_at: ${now}
317
+ created_by: dashboard-dispatch
318
+ assigned_to: ${agent || 'null'}
319
+ dispatch_type: ${data.type}
320
+ ---
321
+
322
+ # ${title}
323
+
324
+ ## Summary
325
+
326
+ ${description}
327
+
328
+ ## Context
329
+
330
+ ${buildContextSection(data, context)}
331
+
332
+ ## Target
333
+
334
+ ${data.target ? `- File/Resource: \`${data.target}\`` : '- No specific target'}
335
+
336
+ ## Acceptance Criteria
337
+
338
+ ${criteria.map(c => `- [ ] ${c}`).join('\n')}
339
+
340
+ ## Dispatch Info
341
+
342
+ - **Dispatched via:** Dashboard Web UI
343
+ - **Dispatch type:** ${data.type}
344
+ - **Suggested agent:** ${agent || 'auto-assign'}
345
+ - **Timestamp:** ${now}
346
+ `;
347
+
348
+ // Ensure directory exists
349
+ if (!fs.existsSync(tasksDir)) {
350
+ fs.mkdirSync(tasksDir, { recursive: true });
351
+ }
352
+
353
+ // Write task file
354
+ fs.writeFileSync(filePath, content, 'utf8');
355
+
356
+ console.log(`[Dispatch] Created task: ${filename}`);
357
+
358
+ // Read back the created task
359
+ const createdTask = tasksApi.readTaskFile(filePath);
360
+
361
+ // Broadcast task creation
362
+ if (broadcast) {
363
+ broadcast({
364
+ type: 'task-created',
365
+ task: createdTask,
366
+ dispatch: {
367
+ type: data.type,
368
+ target: data.target,
369
+ agent: agent
370
+ }
371
+ });
372
+ }
373
+
374
+ return {
375
+ success: true,
376
+ task: createdTask,
377
+ file: filename,
378
+ dispatchType: data.type,
379
+ suggestedAgent: agent
380
+ };
381
+ }
382
+
383
+ /**
384
+ * Map dispatch type to task type
385
+ * @param {string} dispatchType - Dispatch type
386
+ * @returns {string} Task type
387
+ */
388
+ function mapDispatchTypeToTaskType(dispatchType) {
389
+ const typeMap = {
390
+ 'stale-docs': 'documentation',
391
+ 'failing-test': 'bug',
392
+ 'security-issue': 'security',
393
+ 'low-coverage': 'test',
394
+ 'todo-fixme': 'improvement',
395
+ 'pending-review': 'review',
396
+ 'custom': 'task'
397
+ };
398
+ return typeMap[dispatchType] || 'task';
399
+ }
400
+
401
+ /**
402
+ * Build context section for task
403
+ * @param {Object} data - Dispatch data
404
+ * @param {Object} context - Additional context
405
+ * @returns {string} Context section content
406
+ */
407
+ function buildContextSection(data, context) {
408
+ const lines = [];
409
+
410
+ if (data.type === 'stale-docs') {
411
+ lines.push(`This documentation has not been updated recently.`);
412
+ if (context.lastUpdated) {
413
+ lines.push(`Last updated: ${context.lastUpdated}`);
414
+ }
415
+ if (context.daysSinceUpdate) {
416
+ lines.push(`Days since update: ${context.daysSinceUpdate}`);
417
+ }
418
+ }
419
+
420
+ if (data.type === 'failing-test') {
421
+ lines.push(`Test failure detected during automated testing.`);
422
+ if (context.error) {
423
+ lines.push(`\nError message:\n\`\`\`\n${context.error}\n\`\`\``);
424
+ }
425
+ if (context.failureCount) {
426
+ lines.push(`Failure count: ${context.failureCount}`);
427
+ }
428
+ }
429
+
430
+ if (data.type === 'security-issue') {
431
+ lines.push(`Security issue detected.`);
432
+ if (context.vulnerability) {
433
+ lines.push(`Vulnerability: ${context.vulnerability}`);
434
+ }
435
+ if (context.severity) {
436
+ lines.push(`Severity: ${context.severity}`);
437
+ }
438
+ if (context.cve) {
439
+ lines.push(`CVE: ${context.cve}`);
440
+ }
441
+ }
442
+
443
+ if (data.type === 'low-coverage') {
444
+ lines.push(`Code coverage is below acceptable threshold.`);
445
+ if (context.coverage) {
446
+ lines.push(`Current coverage: ${context.coverage}%`);
447
+ }
448
+ if (context.threshold) {
449
+ lines.push(`Required threshold: ${context.threshold}%`);
450
+ }
451
+ }
452
+
453
+ if (context.reason) {
454
+ lines.push(`\nReason: ${context.reason}`);
455
+ }
456
+
457
+ if (context.notes) {
458
+ lines.push(`\nNotes: ${context.notes}`);
459
+ }
460
+
461
+ return lines.length > 0 ? lines.join('\n') : 'Dispatched from dashboard.';
462
+ }
463
+
464
+ /**
465
+ * Get available dispatch types
466
+ * @returns {Object} Dispatch type configurations
467
+ */
468
+ function getDispatchTypes() {
469
+ return Object.entries(DISPATCH_TYPES).map(([key, config]) => ({
470
+ type: key,
471
+ prefix: config.prefix,
472
+ priority: config.priority,
473
+ suggestedAgent: config.suggestedAgent
474
+ }));
475
+ }
476
+
477
+ module.exports = {
478
+ dispatch,
479
+ getDispatchTypes,
480
+ validateDispatchRequest,
481
+ DispatchValidationError,
482
+ DISPATCH_TYPES
483
+ };