@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,52 @@
1
+ <script>
2
+ import { mobileActivePanel, setMobilePanel } from '../stores/ui.js';
3
+
4
+ const tabs = [
5
+ { id: 'tasks', label: 'Tasks', icon: 'M9 11l3 3L22 4M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11' },
6
+ { id: 'agents', label: 'Agents', icon: 'M12 8a5 5 0 1 0 0-10 5 5 0 0 0 0 10zM20 21a8 8 0 0 0-16 0' },
7
+ { id: 'notifications', label: 'Notif', icon: 'M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 0 1-3.46 0' },
8
+ { id: 'issues', label: 'Issues', icon: 'M12 12a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM12 8v4M12 16h.01' },
9
+ ];
10
+
11
+ function handleTabClick(tabId) {
12
+ setMobilePanel(tabId);
13
+ }
14
+ </script>
15
+
16
+ <!-- svelte-ignore a11y_no_noninteractive_element_to_interactive_role -->
17
+ <nav class="mobile-tabs" role="tablist" aria-label="Panel navigation">
18
+ {#each tabs as tab}
19
+ <button
20
+ class="mobile-tab"
21
+ class:active={$mobileActivePanel === tab.id}
22
+ role="tab"
23
+ aria-selected={$mobileActivePanel === tab.id}
24
+ data-panel={tab.id}
25
+ onclick={() => handleTabClick(tab.id)}
26
+ >
27
+ {#if tab.id === 'tasks'}
28
+ <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
29
+ <path d="M9 11l3 3L22 4"/>
30
+ <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
31
+ </svg>
32
+ {:else if tab.id === 'agents'}
33
+ <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
34
+ <circle cx="12" cy="8" r="5"/>
35
+ <path d="M20 21a8 8 0 0 0-16 0"/>
36
+ </svg>
37
+ {:else if tab.id === 'notifications'}
38
+ <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
39
+ <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
40
+ <path d="M13.73 21a2 2 0 0 1-3.46 0"/>
41
+ </svg>
42
+ {:else if tab.id === 'issues'}
43
+ <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
44
+ <circle cx="12" cy="12" r="10"/>
45
+ <line x1="12" y1="8" x2="12" y2="12"/>
46
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
47
+ </svg>
48
+ {/if}
49
+ <span>{tab.label}</span>
50
+ </button>
51
+ {/each}
52
+ </nav>
@@ -0,0 +1,60 @@
1
+ <script>
2
+ import { dismissNotification } from '../stores/notifications.js';
3
+ import { formatRelativeTime, sanitizeClassName } from '../utils/formatters.js';
4
+
5
+ export let notification;
6
+
7
+ const icons = {
8
+ success: '\u2713',
9
+ info: 'i',
10
+ warning: '!',
11
+ attention: '\u{1F514}',
12
+ error: '\u2717',
13
+ };
14
+
15
+ $: type = notification.type || 'info';
16
+ $: sanitizedType = sanitizeClassName(type);
17
+ $: icon = icons[type] || 'i';
18
+
19
+ function handleDismiss(event) {
20
+ event.stopPropagation();
21
+ dismissNotification(notification.id);
22
+ }
23
+
24
+ function handleAction(event, action, index) {
25
+ event.stopPropagation();
26
+ console.log('Notification action:', action, notification.id);
27
+ dismissNotification(notification.id);
28
+ }
29
+ </script>
30
+
31
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
32
+ <div class="card notification-card" tabindex="0" data-notification-id={notification.id}>
33
+ <div class="notification-icon {sanitizedType}">{icon}</div>
34
+ <div class="notification-content">
35
+ <div class="notification-title">{notification.title}</div>
36
+ {#if notification.message}
37
+ <div class="notification-desc">{notification.message}</div>
38
+ {/if}
39
+ <div class="notification-time">{formatRelativeTime(notification.timestamp || notification.created_at)}</div>
40
+ {#if notification.actions && notification.actions.length > 0}
41
+ <div class="notification-actions">
42
+ {#each notification.actions as action, index}
43
+ <button
44
+ class="btn btn-secondary"
45
+ onclick={(e) => handleAction(e, action.action, index)}
46
+ >
47
+ {action.label}
48
+ </button>
49
+ {/each}
50
+ </div>
51
+ {/if}
52
+ </div>
53
+ <button
54
+ class="notification-dismiss"
55
+ onclick={handleDismiss}
56
+ aria-label="Dismiss notification"
57
+ >
58
+ &times;
59
+ </button>
60
+ </div>
@@ -0,0 +1,44 @@
1
+ <script>
2
+ import { notifications, groupedNotifications, clearAllNotifications } from '../stores/notifications.js';
3
+ import NotificationCard from './NotificationCard.svelte';
4
+ </script>
5
+
6
+ <section id="notifications-panel" class="panel notifications-panel" aria-labelledby="notifications-heading" tabindex="-1">
7
+ <div class="panel-header">
8
+ <h2 id="notifications-heading" class="panel-title">NOTIFICATIONS</h2>
9
+ <button class="panel-clear" onclick={clearAllNotifications} aria-label="Clear all notifications">
10
+ Clear
11
+ </button>
12
+ </div>
13
+
14
+ {#if $notifications.length === 0}
15
+ <div class="empty-state">
16
+ <div class="empty-icon" aria-hidden="true">&#x1F514;</div>
17
+ <p class="empty-title">Quiet day!</p>
18
+ <p class="empty-subtitle">No notifications</p>
19
+ </div>
20
+ {:else}
21
+ <div id="notifications-list" class="panel-content notifications-list" role="log" aria-live="polite" aria-busy="false">
22
+ {#if $groupedNotifications.today.length > 0}
23
+ <div class="notification-group-header">Today</div>
24
+ {#each $groupedNotifications.today as notification (notification.id)}
25
+ <NotificationCard {notification} />
26
+ {/each}
27
+ {/if}
28
+
29
+ {#if $groupedNotifications.yesterday.length > 0}
30
+ <div class="notification-group-header">Yesterday</div>
31
+ {#each $groupedNotifications.yesterday as notification (notification.id)}
32
+ <NotificationCard {notification} />
33
+ {/each}
34
+ {/if}
35
+
36
+ {#if $groupedNotifications.older.length > 0}
37
+ <div class="notification-group-header">Earlier</div>
38
+ {#each $groupedNotifications.older as notification (notification.id)}
39
+ <NotificationCard {notification} />
40
+ {/each}
41
+ {/if}
42
+ </div>
43
+ {/if}
44
+ </section>
@@ -0,0 +1,63 @@
1
+ <script>
2
+ import { selectedTaskId, toggleTaskExpand } from '../stores/tasks.js';
3
+ import { getAgentConfig } from '../stores/agents.js';
4
+ import { showToast } from '../stores/toast.js';
5
+ import { formatRelativeTime, truncate, sanitizeClassName } from '../utils/formatters.js';
6
+
7
+ export let task;
8
+
9
+ $: priority = sanitizeClassName((task.priority || 'medium').toLowerCase());
10
+ $: agent = getAgentConfig(task.assigned_to || task.assignee);
11
+ $: isExpanded = $selectedTaskId === task.id;
12
+
13
+ function handleClick() {
14
+ toggleTaskExpand(task.id);
15
+ }
16
+
17
+ function handleKeydown(event) {
18
+ if (event.key === 'Enter' || event.key === ' ') {
19
+ event.preventDefault();
20
+ toggleTaskExpand(task.id);
21
+ }
22
+ }
23
+
24
+ function viewTask(event) {
25
+ event.stopPropagation();
26
+ showToast({
27
+ type: 'info',
28
+ title: 'View Task',
29
+ message: `Open tasks/pending/${task.id}.md in your editor`,
30
+ });
31
+ }
32
+ </script>
33
+
34
+ <div
35
+ class="card task-card"
36
+ class:expanded={isExpanded}
37
+ data-task-id={task.id}
38
+ tabindex="0"
39
+ role="button"
40
+ aria-expanded={isExpanded}
41
+ onclick={handleClick}
42
+ onkeydown={handleKeydown}
43
+ >
44
+ <div class="task-card-header">
45
+ <span class="priority-dot {priority}" title="{task.priority || 'medium'} priority"></span>
46
+ <span class="task-id">{task.id}</span>
47
+ </div>
48
+ <div class="task-title" title={task.title}>{truncate(task.title, 40)}</div>
49
+ <div class="task-meta">
50
+ {#if agent}
51
+ <span class="task-agent">
52
+ <span class="agent-badge {agent.color}">@{agent.short.toLowerCase()}</span>
53
+ </span>
54
+ {/if}
55
+ <span class="task-time">{formatRelativeTime(task.updated_at || task.created_at)}</span>
56
+ </div>
57
+ <div class="task-expanded-content">
58
+ <div class="task-description">{task.description || 'No description provided.'}</div>
59
+ <div class="task-actions">
60
+ <button class="btn btn-secondary task-view-btn" onclick={viewTask}>View File</button>
61
+ </div>
62
+ </div>
63
+ </div>
@@ -0,0 +1,82 @@
1
+ <script>
2
+ import { filteredTasks, tasksLoading, tasksError, currentTaskFilter, taskCounts, setTaskFilter } from '../stores/tasks.js';
3
+ import { refreshTasks } from '../stores/websocket.js';
4
+ import TaskCard from './TaskCard.svelte';
5
+
6
+ let refreshing = false;
7
+
8
+ async function handleRefresh() {
9
+ refreshing = true;
10
+ await refreshTasks();
11
+ refreshing = false;
12
+ }
13
+
14
+ function handleFilterClick(filter) {
15
+ setTaskFilter(filter);
16
+ }
17
+
18
+ const filters = [
19
+ { id: 'pending', label: 'Pending', countKey: 'pending' },
20
+ { id: 'in-progress', label: 'Progress', countKey: 'in-progress' },
21
+ { id: 'review', label: 'Review', countKey: 'review' },
22
+ { id: 'done', label: 'Done', countKey: 'done' },
23
+ ];
24
+ </script>
25
+
26
+ <section id="tasks-panel" class="panel tasks-panel" aria-labelledby="tasks-heading" tabindex="-1">
27
+ <div class="panel-header">
28
+ <h2 id="tasks-heading" class="panel-title">TASKS</h2>
29
+ <button
30
+ class="panel-refresh"
31
+ class:loading={refreshing}
32
+ onclick={handleRefresh}
33
+ aria-label="Refresh tasks"
34
+ >
35
+ <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
36
+ <path d="M23 4v6h-6M1 20v-6h6"/>
37
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
38
+ </svg>
39
+ </button>
40
+ </div>
41
+ <div class="tasks-filters" role="tablist" aria-label="Task status filters">
42
+ {#each filters as filter}
43
+ <button
44
+ class="filter-tab"
45
+ class:active={$currentTaskFilter === filter.id}
46
+ role="tab"
47
+ aria-selected={$currentTaskFilter === filter.id}
48
+ data-filter={filter.id}
49
+ onclick={() => handleFilterClick(filter.id)}
50
+ >
51
+ {filter.label} <span class="filter-count">{$taskCounts[filter.countKey]}</span>
52
+ </button>
53
+ {/each}
54
+ </div>
55
+
56
+ {#if $tasksLoading}
57
+ <div class="loading-state">
58
+ <div class="skeleton-card"></div>
59
+ <div class="skeleton-card"></div>
60
+ <div class="skeleton-card"></div>
61
+ </div>
62
+ {:else if $tasksError}
63
+ <div class="error-state">
64
+ <div class="error-icon" aria-hidden="true">!</div>
65
+ <p class="error-title">Failed to load tasks</p>
66
+ <p class="error-message">{$tasksError}</p>
67
+ <button class="btn btn-secondary" onclick={handleRefresh}>Retry</button>
68
+ </div>
69
+ {:else if $filteredTasks.length === 0}
70
+ <div class="empty-state">
71
+ <div class="empty-icon" aria-hidden="true">&#x2713;</div>
72
+ <p class="empty-title">All caught up!</p>
73
+ <p class="empty-subtitle">No pending tasks</p>
74
+ </div>
75
+ {:else}
76
+ <div id="tasks-list" class="panel-content tasks-list" role="tabpanel" aria-busy="false">
77
+ {#each $filteredTasks as task (task.id)}
78
+ <TaskCard {task} />
79
+ {/each}
80
+ </div>
81
+ {/if}
82
+ </section>
@@ -0,0 +1,45 @@
1
+ <script>
2
+ import { toasts, removeToast } from '../stores/toast.js';
3
+
4
+ const icons = {
5
+ success: '\u2713',
6
+ error: '\u2717',
7
+ info: 'i',
8
+ warning: '!',
9
+ };
10
+
11
+ function handleAction(toast, action, index) {
12
+ if (action.action) {
13
+ action.action();
14
+ }
15
+ removeToast(toast.id);
16
+ }
17
+
18
+ function handleClose(toastId) {
19
+ removeToast(toastId);
20
+ }
21
+ </script>
22
+
23
+ <div id="toast-container" class="toast-container" role="status" aria-live="polite" aria-atomic="true">
24
+ {#each $toasts as toast (toast.id)}
25
+ <div class="toast {toast.type}" id={toast.id}>
26
+ <div class="toast-icon">{icons[toast.type] || 'i'}</div>
27
+ <div class="toast-content">
28
+ <div class="toast-title">{toast.title}</div>
29
+ {#if toast.message}
30
+ <div class="toast-message">{toast.message}</div>
31
+ {/if}
32
+ {#if toast.actions && toast.actions.length > 0}
33
+ <div class="toast-actions">
34
+ {#each toast.actions as action, index}
35
+ <button class="toast-action" onclick={() => handleAction(toast, action, index)}>
36
+ {action.label}
37
+ </button>
38
+ {/each}
39
+ </div>
40
+ {/if}
41
+ </div>
42
+ <button class="toast-close" onclick={() => handleClose(toast.id)} aria-label="Dismiss">&times;</button>
43
+ </div>
44
+ {/each}
45
+ </div>
@@ -0,0 +1,34 @@
1
+ import { writable } from 'svelte/store';
2
+
3
+ // Agent data store
4
+ export const agents = writable([]);
5
+ export const agentsLoading = writable(true);
6
+ export const agentsError = writable(null);
7
+
8
+ // Agent configuration
9
+ export const AGENTS = {
10
+ 'planning-hub': { name: 'Planning Hub', short: 'Hub', icon: 'H', color: 'hub' },
11
+ 'hub': { name: 'Planning Hub', short: 'Hub', icon: 'H', color: 'hub' },
12
+ 'anvil': { name: 'Anvil', short: 'Anvil', icon: 'A', color: 'anvil' },
13
+ 'furnace': { name: 'Furnace', short: 'Furnace', icon: 'F', color: 'furnace' },
14
+ 'crucible': { name: 'Crucible', short: 'Crucible', icon: 'C', color: 'crucible' },
15
+ 'scribe': { name: 'Scribe', short: 'Scribe', icon: 'S', color: 'scribe' },
16
+ 'temper': { name: 'Temper', short: 'Temper', icon: 'T', color: 'temper' },
17
+ 'ember': { name: 'Ember', short: 'Ember', icon: 'E', color: 'ember' },
18
+ 'architect': { name: 'Architect', short: 'Arch', icon: 'Ar', color: 'architect' },
19
+ 'aegis': { name: 'Aegis', short: 'Aegis', icon: 'Ae', color: 'aegis' },
20
+ 'pixel': { name: 'Pixel', short: 'Pixel', icon: 'P', color: 'pixel' },
21
+ 'oracle': { name: 'Oracle', short: 'Oracle', icon: 'O', color: 'oracle' },
22
+ 'loki': { name: 'Loki', short: 'Loki', icon: 'L', color: 'loki' },
23
+ };
24
+
25
+ // Helper to get agent config
26
+ export function getAgentConfig(agentName) {
27
+ if (!agentName) return null;
28
+ return AGENTS[agentName.toLowerCase()] || {
29
+ name: agentName,
30
+ short: agentName.charAt(0).toUpperCase(),
31
+ icon: agentName.charAt(0).toUpperCase(),
32
+ color: '',
33
+ };
34
+ }
@@ -0,0 +1,54 @@
1
+ import { writable } from 'svelte/store';
2
+
3
+ // Issue data store
4
+ export const issues = writable([]);
5
+ export const issuesLoading = writable(true);
6
+ export const issuesError = writable(null);
7
+ export const issuesCollapsed = writable(false);
8
+
9
+ // Dispatch modal state
10
+ export const pendingDispatch = writable(null);
11
+ export const dispatchLoading = writable(false);
12
+ export const dispatchError = writable(null);
13
+
14
+ // Issue category configuration
15
+ export const ISSUE_CATEGORIES = {
16
+ 'doc': { label: 'DOC', icon: 'D', agent: 'scribe', priority: 'medium' },
17
+ 'test': { label: 'TEST', icon: 'T', agent: 'crucible', priority: 'high' },
18
+ 'sec': { label: 'SEC', icon: 'S', agent: 'aegis', priority: 'critical' },
19
+ 'cov': { label: 'COV', icon: 'C', agent: 'crucible', priority: 'medium' },
20
+ 'todo': { label: 'TODO', icon: 'TD', agent: null, priority: 'low' },
21
+ 'review': { label: 'REVIEW', icon: 'R', agent: 'sentinel', priority: 'high' },
22
+ };
23
+
24
+ // Helper to get category config
25
+ export function getCategoryConfig(category) {
26
+ return ISSUE_CATEGORIES[category] || ISSUE_CATEGORIES.todo;
27
+ }
28
+
29
+ // Generate task ID
30
+ export function generateTaskId() {
31
+ const timestamp = Date.now().toString(36);
32
+ const random = Math.random().toString(36).slice(2, 6);
33
+ return `AUTO-${timestamp}-${random}`;
34
+ }
35
+
36
+ // Actions
37
+ export function openDispatchModal(issue) {
38
+ pendingDispatch.set(issue);
39
+ dispatchError.set(null);
40
+ }
41
+
42
+ export function closeDispatchModal() {
43
+ pendingDispatch.set(null);
44
+ dispatchLoading.set(false);
45
+ dispatchError.set(null);
46
+ }
47
+
48
+ export function toggleIssuesCollapse() {
49
+ issuesCollapsed.update(v => !v);
50
+ }
51
+
52
+ export function removeIssue(issueId) {
53
+ issues.update(items => items.filter(i => i.id !== issueId));
54
+ }
@@ -0,0 +1,48 @@
1
+ import { writable, derived } from 'svelte/store';
2
+
3
+ // Notification data store
4
+ export const notifications = writable([]);
5
+
6
+ // Group notifications by date
7
+ export const groupedNotifications = derived(notifications, ($notifications) => {
8
+ const today = new Date().toDateString();
9
+ const yesterday = new Date(Date.now() - 86400000).toDateString();
10
+
11
+ const grouped = {
12
+ today: [],
13
+ yesterday: [],
14
+ older: [],
15
+ };
16
+
17
+ $notifications.forEach(n => {
18
+ const date = new Date(n.timestamp || n.created_at).toDateString();
19
+ if (date === today) grouped.today.push(n);
20
+ else if (date === yesterday) grouped.yesterday.push(n);
21
+ else grouped.older.push(n);
22
+ });
23
+
24
+ return grouped;
25
+ });
26
+
27
+ // Actions
28
+ export function addNotification(notification) {
29
+ const newNotification = {
30
+ id: `notif-${Date.now()}`,
31
+ timestamp: new Date().toISOString(),
32
+ ...notification,
33
+ };
34
+
35
+ notifications.update(items => {
36
+ const updated = [newNotification, ...items];
37
+ // Keep last 50 notifications
38
+ return updated.slice(0, 50);
39
+ });
40
+ }
41
+
42
+ export function dismissNotification(id) {
43
+ notifications.update(items => items.filter(n => n.id !== id));
44
+ }
45
+
46
+ export function clearAllNotifications() {
47
+ notifications.set([]);
48
+ }
@@ -0,0 +1,63 @@
1
+ import { writable, derived } from 'svelte/store';
2
+
3
+ // Task data store
4
+ export const tasks = writable([]);
5
+ export const tasksLoading = writable(true);
6
+ export const tasksError = writable(null);
7
+ export const currentTaskFilter = writable('pending');
8
+ export const selectedTaskId = writable(null);
9
+
10
+ // Derived stores for filtered tasks and counts
11
+ export const filteredTasks = derived(
12
+ [tasks, currentTaskFilter],
13
+ ([$tasks, $filter]) => {
14
+ return $tasks.filter(task => {
15
+ const status = (task.status || '').toLowerCase().replace(/[_\s]/g, '-');
16
+ if ($filter === 'pending') {
17
+ return status === 'pending' || !status;
18
+ }
19
+ if ($filter === 'in-progress') {
20
+ return status === 'in-progress' || status === 'working';
21
+ }
22
+ if ($filter === 'review') {
23
+ return status === 'review' || status === 'in-review' || status === 'needs-review';
24
+ }
25
+ if ($filter === 'done') {
26
+ return status === 'done' || status === 'completed';
27
+ }
28
+ return true;
29
+ });
30
+ }
31
+ );
32
+
33
+ export const taskCounts = derived(tasks, ($tasks) => {
34
+ const counts = {
35
+ pending: 0,
36
+ 'in-progress': 0,
37
+ review: 0,
38
+ done: 0,
39
+ };
40
+
41
+ $tasks.forEach(task => {
42
+ const status = (task.status || '').toLowerCase().replace(/[_\s]/g, '-');
43
+ if (status === 'pending' || !status) counts.pending++;
44
+ else if (status === 'in-progress' || status === 'working') counts['in-progress']++;
45
+ else if (status === 'review' || status === 'in-review' || status === 'needs-review') counts.review++;
46
+ else if (status === 'done' || status === 'completed') counts.done++;
47
+ });
48
+
49
+ return counts;
50
+ });
51
+
52
+ // Actions
53
+ export function setTaskFilter(filter) {
54
+ currentTaskFilter.set(filter);
55
+ }
56
+
57
+ export function toggleTaskExpand(taskId) {
58
+ selectedTaskId.update(current => current === taskId ? null : taskId);
59
+ }
60
+
61
+ export function clearSelectedTask() {
62
+ selectedTaskId.set(null);
63
+ }
@@ -0,0 +1,33 @@
1
+ import { writable } from 'svelte/store';
2
+
3
+ const STORAGE_KEY = 'vibe-forge-theme';
4
+
5
+ // Get initial theme from localStorage or default to dark
6
+ function getInitialTheme() {
7
+ if (typeof localStorage !== 'undefined') {
8
+ return localStorage.getItem(STORAGE_KEY) || 'dark';
9
+ }
10
+ return 'dark';
11
+ }
12
+
13
+ // Theme store
14
+ export const theme = writable(getInitialTheme());
15
+
16
+ // Initialize theme on document
17
+ export function initTheme() {
18
+ const savedTheme = getInitialTheme();
19
+ document.documentElement.setAttribute('data-theme', savedTheme);
20
+ theme.set(savedTheme);
21
+ }
22
+
23
+ // Toggle theme
24
+ export function toggleTheme() {
25
+ theme.update(current => {
26
+ const newTheme = current === 'dark' ? 'light' : 'dark';
27
+ document.documentElement.setAttribute('data-theme', newTheme);
28
+ if (typeof localStorage !== 'undefined') {
29
+ localStorage.setItem(STORAGE_KEY, newTheme);
30
+ }
31
+ return newTheme;
32
+ });
33
+ }
@@ -0,0 +1,35 @@
1
+ import { writable } from 'svelte/store';
2
+
3
+ const TOAST_DURATION = 5000;
4
+
5
+ // Toast store
6
+ export const toasts = writable([]);
7
+
8
+ // Show toast
9
+ export function showToast({ type = 'info', title, message, actions = [], duration = TOAST_DURATION }) {
10
+ const toastId = `toast-${Date.now()}`;
11
+
12
+ const toast = {
13
+ id: toastId,
14
+ type,
15
+ title,
16
+ message,
17
+ actions,
18
+ };
19
+
20
+ toasts.update(items => [...items, toast]);
21
+
22
+ // Auto-dismiss after duration
23
+ if (duration > 0) {
24
+ setTimeout(() => {
25
+ removeToast(toastId);
26
+ }, duration);
27
+ }
28
+
29
+ return toastId;
30
+ }
31
+
32
+ // Remove toast
33
+ export function removeToast(toastId) {
34
+ toasts.update(items => items.filter(t => t.id !== toastId));
35
+ }
@@ -0,0 +1,25 @@
1
+ import { writable } from 'svelte/store';
2
+
3
+ // UI State
4
+ export const focusedPanel = writable(null);
5
+ export const focusedItemIndex = writable(-1);
6
+ export const mobileActivePanel = writable('tasks');
7
+ export const showKeyboardHelp = writable(false);
8
+
9
+ // Actions
10
+ export function setFocusedPanel(panel) {
11
+ focusedPanel.set(panel);
12
+ focusedItemIndex.set(-1);
13
+ }
14
+
15
+ export function setMobilePanel(panel) {
16
+ mobileActivePanel.set(panel);
17
+ }
18
+
19
+ export function openKeyboardHelp() {
20
+ showKeyboardHelp.set(true);
21
+ }
22
+
23
+ export function closeKeyboardHelp() {
24
+ showKeyboardHelp.set(false);
25
+ }