@agile-vibe-coding/avc 0.1.1 → 0.3.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 (239) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +152 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/code-implementer.md +117 -0
  5. package/cli/agents/code-validator.md +80 -0
  6. package/cli/agents/context-reviewer-epic.md +101 -0
  7. package/cli/agents/context-reviewer-story.md +92 -0
  8. package/cli/agents/context-writer-epic.md +145 -0
  9. package/cli/agents/context-writer-story.md +111 -0
  10. package/cli/agents/database-deep-dive.md +470 -0
  11. package/cli/agents/database-recommender.md +634 -0
  12. package/cli/agents/doc-distributor.md +176 -0
  13. package/cli/agents/doc-writer-epic.md +42 -0
  14. package/cli/agents/doc-writer-story.md +43 -0
  15. package/cli/agents/documentation-updater.md +203 -0
  16. package/cli/agents/duplicate-detector.md +110 -0
  17. package/cli/agents/epic-story-decomposer.md +559 -0
  18. package/cli/agents/feature-context-generator.md +91 -0
  19. package/cli/agents/gap-checker-epic.md +52 -0
  20. package/cli/agents/impact-checker-story.md +51 -0
  21. package/cli/agents/migration-guide-generator.md +305 -0
  22. package/cli/agents/mission-scope-generator.md +143 -0
  23. package/cli/agents/mission-scope-validator.md +146 -0
  24. package/cli/agents/project-context-extractor.md +122 -0
  25. package/cli/agents/project-documentation-creator.json +226 -0
  26. package/cli/agents/project-documentation-creator.md +595 -0
  27. package/cli/agents/question-prefiller.md +269 -0
  28. package/cli/agents/refiner-epic.md +39 -0
  29. package/cli/agents/refiner-story.md +42 -0
  30. package/cli/agents/scaffolding-generator.md +99 -0
  31. package/cli/agents/seed-validator.md +71 -0
  32. package/cli/agents/story-doc-enricher.md +133 -0
  33. package/cli/agents/story-scope-reviewer.md +147 -0
  34. package/cli/agents/story-splitter.md +83 -0
  35. package/cli/agents/suggestion-business-analyst.md +88 -0
  36. package/cli/agents/suggestion-deployment-architect.md +263 -0
  37. package/cli/agents/suggestion-product-manager.md +129 -0
  38. package/cli/agents/suggestion-security-specialist.md +156 -0
  39. package/cli/agents/suggestion-technical-architect.md +269 -0
  40. package/cli/agents/suggestion-ux-researcher.md +93 -0
  41. package/cli/agents/task-subtask-decomposer.md +188 -0
  42. package/cli/agents/validator-documentation.json +183 -0
  43. package/cli/agents/validator-documentation.md +455 -0
  44. package/cli/agents/validator-selector.md +211 -0
  45. package/cli/ansi-colors.js +21 -0
  46. package/cli/api-reference-tool.js +368 -0
  47. package/cli/build-docs.js +29 -8
  48. package/cli/ceremony-history.js +369 -0
  49. package/cli/checks/catalog.json +76 -0
  50. package/cli/checks/code/quality.json +26 -0
  51. package/cli/checks/code/testing.json +14 -0
  52. package/cli/checks/code/traceability.json +26 -0
  53. package/cli/checks/cross-refs/epic.json +171 -0
  54. package/cli/checks/cross-refs/story.json +149 -0
  55. package/cli/checks/epic/api.json +114 -0
  56. package/cli/checks/epic/backend.json +126 -0
  57. package/cli/checks/epic/cloud.json +126 -0
  58. package/cli/checks/epic/data.json +102 -0
  59. package/cli/checks/epic/database.json +114 -0
  60. package/cli/checks/epic/developer.json +182 -0
  61. package/cli/checks/epic/devops.json +174 -0
  62. package/cli/checks/epic/frontend.json +162 -0
  63. package/cli/checks/epic/mobile.json +102 -0
  64. package/cli/checks/epic/qa.json +90 -0
  65. package/cli/checks/epic/security.json +184 -0
  66. package/cli/checks/epic/solution-architect.json +192 -0
  67. package/cli/checks/epic/test-architect.json +90 -0
  68. package/cli/checks/epic/ui.json +102 -0
  69. package/cli/checks/epic/ux.json +90 -0
  70. package/cli/checks/fixes/epic-fix-template.md +10 -0
  71. package/cli/checks/fixes/story-fix-template.md +10 -0
  72. package/cli/checks/story/api.json +186 -0
  73. package/cli/checks/story/backend.json +102 -0
  74. package/cli/checks/story/cloud.json +102 -0
  75. package/cli/checks/story/data.json +210 -0
  76. package/cli/checks/story/database.json +102 -0
  77. package/cli/checks/story/developer.json +168 -0
  78. package/cli/checks/story/devops.json +102 -0
  79. package/cli/checks/story/frontend.json +174 -0
  80. package/cli/checks/story/mobile.json +102 -0
  81. package/cli/checks/story/qa.json +210 -0
  82. package/cli/checks/story/security.json +198 -0
  83. package/cli/checks/story/solution-architect.json +230 -0
  84. package/cli/checks/story/test-architect.json +210 -0
  85. package/cli/checks/story/ui.json +102 -0
  86. package/cli/checks/story/ux.json +102 -0
  87. package/cli/coding-order.js +401 -0
  88. package/cli/command-logger.js +49 -12
  89. package/cli/components/static-output.js +63 -0
  90. package/cli/console-output-manager.js +94 -0
  91. package/cli/dependency-checker.js +72 -0
  92. package/cli/docs-sync.js +306 -0
  93. package/cli/epic-story-validator.js +659 -0
  94. package/cli/evaluation-prompts.js +1008 -0
  95. package/cli/execution-context.js +195 -0
  96. package/cli/generate-summary-table.js +340 -0
  97. package/cli/init-model-config.js +704 -0
  98. package/cli/init.js +1737 -278
  99. package/cli/kanban-server-manager.js +227 -0
  100. package/cli/llm-claude.js +150 -1
  101. package/cli/llm-gemini.js +109 -0
  102. package/cli/llm-local.js +493 -0
  103. package/cli/llm-mock.js +233 -0
  104. package/cli/llm-openai.js +454 -0
  105. package/cli/llm-provider.js +379 -3
  106. package/cli/llm-token-limits.js +211 -0
  107. package/cli/llm-verifier.js +662 -0
  108. package/cli/llm-xiaomi.js +143 -0
  109. package/cli/message-constants.js +49 -0
  110. package/cli/message-manager.js +334 -0
  111. package/cli/message-types.js +96 -0
  112. package/cli/messaging-api.js +291 -0
  113. package/cli/micro-check-fixer.js +335 -0
  114. package/cli/micro-check-runner.js +449 -0
  115. package/cli/micro-check-scorer.js +148 -0
  116. package/cli/micro-check-validator.js +538 -0
  117. package/cli/model-pricing.js +192 -0
  118. package/cli/model-query-engine.js +468 -0
  119. package/cli/model-recommendation-analyzer.js +495 -0
  120. package/cli/model-selector.js +270 -0
  121. package/cli/output-buffer.js +107 -0
  122. package/cli/process-manager.js +73 -2
  123. package/cli/prompt-logger.js +57 -0
  124. package/cli/repl-ink.js +4625 -1094
  125. package/cli/repl-old.js +3 -4
  126. package/cli/seed-processor.js +962 -0
  127. package/cli/sprint-planning-processor.js +4162 -0
  128. package/cli/template-processor.js +2149 -105
  129. package/cli/templates/project.md +25 -8
  130. package/cli/templates/vitepress-config.mts.template +5 -4
  131. package/cli/token-tracker.js +547 -0
  132. package/cli/tools/generate-story-validators.js +317 -0
  133. package/cli/tools/generate-validators.js +669 -0
  134. package/cli/update-checker.js +19 -17
  135. package/cli/update-notifier.js +4 -4
  136. package/cli/validation-router.js +667 -0
  137. package/cli/verification-tracker.js +563 -0
  138. package/cli/worktree-runner.js +654 -0
  139. package/kanban/README.md +386 -0
  140. package/kanban/client/README.md +205 -0
  141. package/kanban/client/components.json +20 -0
  142. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  143. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  144. package/kanban/client/dist/index.html +16 -0
  145. package/kanban/client/dist/vite.svg +1 -0
  146. package/kanban/client/index.html +15 -0
  147. package/kanban/client/package-lock.json +9442 -0
  148. package/kanban/client/package.json +44 -0
  149. package/kanban/client/postcss.config.js +6 -0
  150. package/kanban/client/public/vite.svg +1 -0
  151. package/kanban/client/src/App.jsx +651 -0
  152. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  153. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
  154. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
  155. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
  156. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  157. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  158. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
  159. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
  160. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  161. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
  162. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  163. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  164. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  165. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
  166. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
  167. package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
  168. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  169. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  170. package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
  171. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  172. package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
  173. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  174. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
  175. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  176. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  177. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  178. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  179. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  180. package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
  181. package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
  182. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
  183. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  184. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
  185. package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
  186. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  187. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  188. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  189. package/kanban/client/src/components/stats/CostModal.jsx +384 -0
  190. package/kanban/client/src/components/ui/badge.jsx +27 -0
  191. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  192. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  193. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  194. package/kanban/client/src/hooks/useGrouping.js +177 -0
  195. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  196. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  197. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  198. package/kanban/client/src/lib/api.js +515 -0
  199. package/kanban/client/src/lib/status-grouping.js +154 -0
  200. package/kanban/client/src/lib/utils.js +11 -0
  201. package/kanban/client/src/main.jsx +10 -0
  202. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  203. package/kanban/client/src/store/ceremonyStore.js +172 -0
  204. package/kanban/client/src/store/filterStore.js +201 -0
  205. package/kanban/client/src/store/kanbanStore.js +123 -0
  206. package/kanban/client/src/store/processStore.js +65 -0
  207. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  208. package/kanban/client/src/styles/globals.css +59 -0
  209. package/kanban/client/tailwind.config.js +77 -0
  210. package/kanban/client/vite.config.js +28 -0
  211. package/kanban/client/vitest.config.js +28 -0
  212. package/kanban/dev-start.sh +47 -0
  213. package/kanban/package.json +12 -0
  214. package/kanban/server/index.js +537 -0
  215. package/kanban/server/routes/ceremony.js +454 -0
  216. package/kanban/server/routes/costs.js +163 -0
  217. package/kanban/server/routes/openai-oauth.js +366 -0
  218. package/kanban/server/routes/processes.js +50 -0
  219. package/kanban/server/routes/settings.js +736 -0
  220. package/kanban/server/routes/websocket.js +281 -0
  221. package/kanban/server/routes/work-items.js +487 -0
  222. package/kanban/server/services/CeremonyService.js +1441 -0
  223. package/kanban/server/services/FileSystemScanner.js +95 -0
  224. package/kanban/server/services/FileWatcher.js +144 -0
  225. package/kanban/server/services/HierarchyBuilder.js +196 -0
  226. package/kanban/server/services/ProcessRegistry.js +122 -0
  227. package/kanban/server/services/TaskRunnerService.js +261 -0
  228. package/kanban/server/services/WorkItemReader.js +123 -0
  229. package/kanban/server/services/WorkItemRefineService.js +510 -0
  230. package/kanban/server/start.js +49 -0
  231. package/kanban/server/utils/kanban-logger.js +132 -0
  232. package/kanban/server/utils/markdown.js +91 -0
  233. package/kanban/server/utils/status-grouping.js +107 -0
  234. package/kanban/server/workers/run-task-worker.js +121 -0
  235. package/kanban/server/workers/seed-worker.js +94 -0
  236. package/kanban/server/workers/sponsor-call-worker.js +92 -0
  237. package/kanban/server/workers/sprint-planning-worker.js +212 -0
  238. package/package.json +19 -7
  239. package/cli/agents/documentation.md +0 -302
@@ -0,0 +1,120 @@
1
+ import { useEffect, useRef, useCallback, useState } from 'react';
2
+
3
+ // Module-level constant — window.location.host never changes during a session
4
+ const WS_URL = import.meta.env.VITE_WS_URL || `ws://${window.location.host}/ws`;
5
+
6
+ const MAX_RECONNECT_ATTEMPTS = 5;
7
+ const RECONNECT_DELAY = 2000;
8
+
9
+ /**
10
+ * WebSocket hook for real-time updates.
11
+ *
12
+ * Returns wsStatus: 'connecting' | 'connected' | 'disconnected'
13
+ * Callbacks are stored in refs so connect() is stable and the effect
14
+ * only runs once (no infinite reconnect loop from inline callbacks).
15
+ */
16
+ export function useWebSocket(options = {}) {
17
+ const { onMessage, onConnected, onDisconnected, onError } = options;
18
+
19
+ const [wsStatus, setWsStatus] = useState('connecting');
20
+
21
+ const wsRef = useRef(null);
22
+ const reconnectTimeoutRef = useRef(null);
23
+ const reconnectAttempts = useRef(0);
24
+
25
+ // Stable refs — updated every render without triggering reconnects
26
+ const onMessageRef = useRef(onMessage);
27
+ const onConnectedRef = useRef(onConnected);
28
+ const onDisconnectedRef = useRef(onDisconnected);
29
+ const onErrorRef = useRef(onError);
30
+ onMessageRef.current = onMessage;
31
+ onConnectedRef.current = onConnected;
32
+ onDisconnectedRef.current = onDisconnected;
33
+ onErrorRef.current = onError;
34
+
35
+ const connect = useCallback(() => {
36
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
37
+ return; // Already connected
38
+ }
39
+
40
+ try {
41
+ const ws = new WebSocket(WS_URL);
42
+
43
+ ws.onopen = () => {
44
+ console.log('WebSocket connected');
45
+ reconnectAttempts.current = 0;
46
+ setWsStatus('connected');
47
+ onConnectedRef.current?.();
48
+ };
49
+
50
+ ws.onmessage = (event) => {
51
+ try {
52
+ const data = JSON.parse(event.data);
53
+ onMessageRef.current?.(data);
54
+ } catch (error) {
55
+ console.error('WebSocket message parse error:', error);
56
+ }
57
+ };
58
+
59
+ ws.onerror = (error) => {
60
+ console.error('WebSocket error:', error);
61
+ onErrorRef.current?.(error);
62
+ };
63
+
64
+ ws.onclose = () => {
65
+ console.log('WebSocket disconnected');
66
+ wsRef.current = null;
67
+ onDisconnectedRef.current?.();
68
+
69
+ if (reconnectAttempts.current < MAX_RECONNECT_ATTEMPTS) {
70
+ reconnectAttempts.current++;
71
+ console.log(`Reconnecting... (attempt ${reconnectAttempts.current})`);
72
+ setWsStatus('connecting');
73
+ reconnectTimeoutRef.current = setTimeout(connect, RECONNECT_DELAY);
74
+ } else {
75
+ console.error('Max reconnection attempts reached');
76
+ setWsStatus('disconnected');
77
+ }
78
+ };
79
+
80
+ wsRef.current = ws;
81
+ } catch (error) {
82
+ console.error('WebSocket connection error:', error);
83
+ onErrorRef.current?.(error);
84
+ }
85
+ }, []); // No callback deps — refs keep them current without recreating connect
86
+
87
+ const disconnect = useCallback(() => {
88
+ if (reconnectTimeoutRef.current) {
89
+ clearTimeout(reconnectTimeoutRef.current);
90
+ reconnectTimeoutRef.current = null;
91
+ }
92
+
93
+ if (wsRef.current) {
94
+ wsRef.current.close();
95
+ wsRef.current = null;
96
+ }
97
+ }, []);
98
+
99
+ const send = useCallback((data) => {
100
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
101
+ wsRef.current.send(JSON.stringify(data));
102
+ } else {
103
+ console.warn('WebSocket not connected, cannot send message');
104
+ }
105
+ }, []);
106
+
107
+ useEffect(() => {
108
+ connect();
109
+ return () => {
110
+ disconnect();
111
+ };
112
+ }, [connect, disconnect]); // both stable — effect runs exactly once
113
+
114
+ return {
115
+ wsStatus,
116
+ send,
117
+ disconnect,
118
+ reconnect: connect,
119
+ };
120
+ }
@@ -0,0 +1,196 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import {
3
+ getHealth,
4
+ getStats,
5
+ getWorkItems,
6
+ getWorkItemsGrouped,
7
+ getWorkItem,
8
+ getWorkItemDoc,
9
+ } from '../api';
10
+
11
+ // Mock fetch globally
12
+ global.fetch = vi.fn();
13
+
14
+ describe('api', () => {
15
+ beforeEach(() => {
16
+ vi.clearAllMocks();
17
+ });
18
+
19
+ describe('getHealth', () => {
20
+ it('should fetch health status successfully', async () => {
21
+ const mockResponse = { status: 'ok', uptime: 12345 };
22
+ global.fetch.mockResolvedValueOnce({
23
+ ok: true,
24
+ json: async () => mockResponse,
25
+ });
26
+
27
+ const result = await getHealth();
28
+
29
+ expect(fetch).toHaveBeenCalledWith('/api/health', {
30
+ headers: { 'Content-Type': 'application/json' },
31
+ });
32
+ expect(result).toEqual(mockResponse);
33
+ });
34
+
35
+ it('should throw error when fetch fails', async () => {
36
+ global.fetch.mockResolvedValueOnce({
37
+ ok: false,
38
+ status: 500,
39
+ statusText: 'Internal Server Error',
40
+ json: async () => ({ error: 'Server error' }),
41
+ });
42
+
43
+ await expect(getHealth()).rejects.toThrow();
44
+ });
45
+ });
46
+
47
+ describe('getStats', () => {
48
+ it('should fetch statistics successfully', async () => {
49
+ const mockStats = {
50
+ totalItems: 42,
51
+ byStatus: { planned: 10, implementing: 15 },
52
+ byType: { epic: 5, story: 20 },
53
+ };
54
+ global.fetch.mockResolvedValueOnce({
55
+ ok: true,
56
+ json: async () => mockStats,
57
+ });
58
+
59
+ const result = await getStats();
60
+
61
+ expect(fetch).toHaveBeenCalledWith('/api/stats', {
62
+ headers: { 'Content-Type': 'application/json' },
63
+ });
64
+ expect(result).toEqual(mockStats);
65
+ });
66
+ });
67
+
68
+ describe('getWorkItems', () => {
69
+ it('should fetch work items without filters', async () => {
70
+ const mockItems = [
71
+ { id: 'EPIC-001', type: 'epic', status: 'implementing' },
72
+ { id: 'STORY-001', type: 'story', status: 'ready' },
73
+ ];
74
+ global.fetch.mockResolvedValueOnce({
75
+ ok: true,
76
+ json: async () => mockItems,
77
+ });
78
+
79
+ const result = await getWorkItems();
80
+
81
+ expect(fetch).toHaveBeenCalledWith('/api/work-items', {
82
+ headers: { 'Content-Type': 'application/json' },
83
+ });
84
+ expect(result).toEqual(mockItems);
85
+ });
86
+
87
+ it('should fetch work items with type filter', async () => {
88
+ const mockItems = [{ id: 'EPIC-001', type: 'epic', status: 'implementing' }];
89
+ global.fetch.mockResolvedValueOnce({
90
+ ok: true,
91
+ json: async () => mockItems,
92
+ });
93
+
94
+ const result = await getWorkItems({ type: 'epic' });
95
+
96
+ expect(fetch).toHaveBeenCalledWith('/api/work-items?type=epic', {
97
+ headers: { 'Content-Type': 'application/json' },
98
+ });
99
+ expect(result).toEqual(mockItems);
100
+ });
101
+
102
+ it('should fetch work items with multiple filters', async () => {
103
+ const mockItems = [{ id: 'STORY-001', type: 'story', status: 'ready' }];
104
+ global.fetch.mockResolvedValueOnce({
105
+ ok: true,
106
+ json: async () => mockItems,
107
+ });
108
+
109
+ const result = await getWorkItems({ type: 'story', status: 'ready' });
110
+
111
+ expect(fetch).toHaveBeenCalledWith('/api/work-items?type=story&status=ready', {
112
+ headers: { 'Content-Type': 'application/json' },
113
+ });
114
+ expect(result).toEqual(mockItems);
115
+ });
116
+ });
117
+
118
+ describe('getWorkItemsGrouped', () => {
119
+ it('should fetch grouped work items', async () => {
120
+ const mockGrouped = {
121
+ Backlog: [{ id: 'STORY-001', status: 'planned' }],
122
+ Ready: [{ id: 'STORY-002', status: 'ready' }],
123
+ };
124
+ global.fetch.mockResolvedValueOnce({
125
+ ok: true,
126
+ json: async () => mockGrouped,
127
+ });
128
+
129
+ const result = await getWorkItemsGrouped();
130
+
131
+ expect(fetch).toHaveBeenCalledWith('/api/work-items/grouped', {
132
+ headers: { 'Content-Type': 'application/json' },
133
+ });
134
+ expect(result).toEqual(mockGrouped);
135
+ });
136
+ });
137
+
138
+ describe('getWorkItem', () => {
139
+ it('should fetch single work item successfully', async () => {
140
+ const mockItem = {
141
+ id: 'EPIC-001',
142
+ type: 'epic',
143
+ name: 'Test Epic',
144
+ status: 'implementing',
145
+ };
146
+ global.fetch.mockResolvedValueOnce({
147
+ ok: true,
148
+ json: async () => mockItem,
149
+ });
150
+
151
+ const result = await getWorkItem('EPIC-001');
152
+
153
+ expect(fetch).toHaveBeenCalledWith('/api/work-items/EPIC-001', {
154
+ headers: { 'Content-Type': 'application/json' },
155
+ });
156
+ expect(result).toEqual(mockItem);
157
+ });
158
+
159
+ it('should throw error for non-existent work item', async () => {
160
+ global.fetch.mockResolvedValueOnce({
161
+ ok: false,
162
+ status: 404,
163
+ statusText: 'Not Found',
164
+ json: async () => ({ error: 'Not found' }),
165
+ });
166
+
167
+ await expect(getWorkItem('NONEXISTENT')).rejects.toThrow();
168
+ });
169
+ });
170
+
171
+ describe('getWorkItemDoc', () => {
172
+ it('should fetch work item documentation', async () => {
173
+ const mockDoc = '# Epic Documentation\n\nDetails here...';
174
+ global.fetch.mockResolvedValueOnce({
175
+ ok: true,
176
+ text: async () => mockDoc,
177
+ });
178
+
179
+ const result = await getWorkItemDoc('EPIC-001');
180
+
181
+ expect(result).toBe(mockDoc);
182
+ });
183
+
184
+ it('should return empty string when doc not found', async () => {
185
+ global.fetch.mockResolvedValueOnce({
186
+ ok: false,
187
+ status: 404,
188
+ });
189
+
190
+ const result = await getWorkItemDoc('EPIC-001');
191
+
192
+ expect(result).toBe('');
193
+ });
194
+ });
195
+
196
+ });
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ STATUS_COLUMN_MAPPING,
4
+ STATUS_METADATA,
5
+ COLUMN_ORDER,
6
+ getColumnForStatus,
7
+ getStatusMetadata,
8
+ groupItemsByColumn,
9
+ getColumnStats,
10
+ } from '../status-grouping';
11
+
12
+ describe('status-grouping', () => {
13
+ describe('getColumnForStatus', () => {
14
+ it('should return correct column for each status', () => {
15
+ expect(getColumnForStatus('planned')).toBe('Backlog');
16
+ expect(getColumnForStatus('pending')).toBe('Backlog');
17
+ expect(getColumnForStatus('ready')).toBe('Ready');
18
+ expect(getColumnForStatus('implementing')).toBe('In Progress');
19
+ expect(getColumnForStatus('feedback')).toBe('In Progress');
20
+ expect(getColumnForStatus('implemented')).toBe('Review');
21
+ expect(getColumnForStatus('testing')).toBe('Review');
22
+ expect(getColumnForStatus('completed')).toBe('Done');
23
+ });
24
+
25
+ it('should return null for unknown status', () => {
26
+ expect(getColumnForStatus('unknown')).toBeNull();
27
+ });
28
+ });
29
+
30
+ describe('getStatusMetadata', () => {
31
+ it('should return metadata for valid status', () => {
32
+ const metadata = getStatusMetadata('ready');
33
+ expect(metadata).toHaveProperty('color');
34
+ expect(metadata).toHaveProperty('icon');
35
+ expect(metadata).toHaveProperty('label');
36
+ expect(metadata.color).toBe('blue');
37
+ expect(metadata.label).toBe('Ready');
38
+ });
39
+
40
+ it('should return null for unknown status', () => {
41
+ expect(getStatusMetadata('unknown')).toBeNull();
42
+ });
43
+ });
44
+
45
+ describe('groupItemsByColumn', () => {
46
+ it('should group items by column correctly', () => {
47
+ const workItems = [
48
+ { id: '1', status: 'planned' },
49
+ { id: '2', status: 'ready' },
50
+ { id: '3', status: 'implementing' },
51
+ { id: '4', status: 'completed' },
52
+ { id: '5', status: 'pending' },
53
+ ];
54
+
55
+ const grouped = groupItemsByColumn(workItems);
56
+
57
+ expect(grouped.Backlog).toHaveLength(2);
58
+ expect(grouped.Ready).toHaveLength(1);
59
+ expect(grouped['In Progress']).toHaveLength(1);
60
+ expect(grouped.Review).toHaveLength(0);
61
+ expect(grouped.Done).toHaveLength(1);
62
+ });
63
+
64
+ it('should initialize all columns as empty arrays', () => {
65
+ const grouped = groupItemsByColumn([]);
66
+
67
+ COLUMN_ORDER.forEach((column) => {
68
+ expect(grouped[column]).toEqual([]);
69
+ });
70
+ });
71
+ });
72
+
73
+ describe('getColumnStats', () => {
74
+ it('should calculate correct statistics', () => {
75
+ const workItems = [
76
+ { status: 'planned' },
77
+ { status: 'planned' },
78
+ { status: 'pending' },
79
+ ];
80
+
81
+ const stats = getColumnStats(workItems);
82
+
83
+ expect(stats.total).toBe(3);
84
+ expect(stats.byStatus.planned).toBe(2);
85
+ expect(stats.byStatus.pending).toBe(1);
86
+ });
87
+
88
+ it('should handle empty array', () => {
89
+ const stats = getColumnStats([]);
90
+ expect(stats.total).toBe(0);
91
+ expect(stats.byStatus).toEqual({});
92
+ });
93
+ });
94
+ });