@agile-vibe-coding/avc 0.1.1 β 0.2.3
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.
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +129 -0
- package/cli/agents/architecture-recommender.md +418 -0
- package/cli/agents/database-deep-dive.md +470 -0
- package/cli/agents/database-recommender.md +634 -0
- package/cli/agents/doc-distributor.md +176 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/epic-story-decomposer.md +280 -0
- package/cli/agents/feature-context-generator.md +91 -0
- package/cli/agents/gap-checker-epic.md +52 -0
- package/cli/agents/impact-checker-story.md +51 -0
- package/cli/agents/migration-guide-generator.md +305 -0
- package/cli/agents/mission-scope-generator.md +79 -0
- package/cli/agents/mission-scope-validator.md +112 -0
- package/cli/agents/project-context-extractor.md +107 -0
- package/cli/agents/project-documentation-creator.json +226 -0
- package/cli/agents/project-documentation-creator.md +595 -0
- package/cli/agents/question-prefiller.md +269 -0
- package/cli/agents/refiner-epic.md +39 -0
- package/cli/agents/refiner-story.md +42 -0
- package/cli/agents/solver-epic-api.json +15 -0
- package/cli/agents/solver-epic-api.md +39 -0
- package/cli/agents/solver-epic-backend.json +15 -0
- package/cli/agents/solver-epic-backend.md +39 -0
- package/cli/agents/solver-epic-cloud.json +15 -0
- package/cli/agents/solver-epic-cloud.md +39 -0
- package/cli/agents/solver-epic-data.json +15 -0
- package/cli/agents/solver-epic-data.md +39 -0
- package/cli/agents/solver-epic-database.json +15 -0
- package/cli/agents/solver-epic-database.md +39 -0
- package/cli/agents/solver-epic-developer.json +15 -0
- package/cli/agents/solver-epic-developer.md +39 -0
- package/cli/agents/solver-epic-devops.json +15 -0
- package/cli/agents/solver-epic-devops.md +39 -0
- package/cli/agents/solver-epic-frontend.json +15 -0
- package/cli/agents/solver-epic-frontend.md +39 -0
- package/cli/agents/solver-epic-mobile.json +15 -0
- package/cli/agents/solver-epic-mobile.md +39 -0
- package/cli/agents/solver-epic-qa.json +15 -0
- package/cli/agents/solver-epic-qa.md +39 -0
- package/cli/agents/solver-epic-security.json +15 -0
- package/cli/agents/solver-epic-security.md +39 -0
- package/cli/agents/solver-epic-solution-architect.json +15 -0
- package/cli/agents/solver-epic-solution-architect.md +39 -0
- package/cli/agents/solver-epic-test-architect.json +15 -0
- package/cli/agents/solver-epic-test-architect.md +39 -0
- package/cli/agents/solver-epic-ui.json +15 -0
- package/cli/agents/solver-epic-ui.md +39 -0
- package/cli/agents/solver-epic-ux.json +15 -0
- package/cli/agents/solver-epic-ux.md +39 -0
- package/cli/agents/solver-story-api.json +15 -0
- package/cli/agents/solver-story-api.md +39 -0
- package/cli/agents/solver-story-backend.json +15 -0
- package/cli/agents/solver-story-backend.md +39 -0
- package/cli/agents/solver-story-cloud.json +15 -0
- package/cli/agents/solver-story-cloud.md +39 -0
- package/cli/agents/solver-story-data.json +15 -0
- package/cli/agents/solver-story-data.md +39 -0
- package/cli/agents/solver-story-database.json +15 -0
- package/cli/agents/solver-story-database.md +39 -0
- package/cli/agents/solver-story-developer.json +15 -0
- package/cli/agents/solver-story-developer.md +39 -0
- package/cli/agents/solver-story-devops.json +15 -0
- package/cli/agents/solver-story-devops.md +39 -0
- package/cli/agents/solver-story-frontend.json +15 -0
- package/cli/agents/solver-story-frontend.md +39 -0
- package/cli/agents/solver-story-mobile.json +15 -0
- package/cli/agents/solver-story-mobile.md +39 -0
- package/cli/agents/solver-story-qa.json +15 -0
- package/cli/agents/solver-story-qa.md +39 -0
- package/cli/agents/solver-story-security.json +15 -0
- package/cli/agents/solver-story-security.md +39 -0
- package/cli/agents/solver-story-solution-architect.json +15 -0
- package/cli/agents/solver-story-solution-architect.md +39 -0
- package/cli/agents/solver-story-test-architect.json +15 -0
- package/cli/agents/solver-story-test-architect.md +39 -0
- package/cli/agents/solver-story-ui.json +15 -0
- package/cli/agents/solver-story-ui.md +39 -0
- package/cli/agents/solver-story-ux.json +15 -0
- package/cli/agents/solver-story-ux.md +39 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/suggestion-business-analyst.md +88 -0
- package/cli/agents/suggestion-deployment-architect.md +263 -0
- package/cli/agents/suggestion-product-manager.md +129 -0
- package/cli/agents/suggestion-security-specialist.md +156 -0
- package/cli/agents/suggestion-technical-architect.md +269 -0
- package/cli/agents/suggestion-ux-researcher.md +93 -0
- package/cli/agents/task-subtask-decomposer.md +188 -0
- package/cli/agents/validator-documentation.json +152 -0
- package/cli/agents/validator-documentation.md +453 -0
- package/cli/agents/validator-epic-api.json +93 -0
- package/cli/agents/validator-epic-api.md +137 -0
- package/cli/agents/validator-epic-backend.json +93 -0
- package/cli/agents/validator-epic-backend.md +130 -0
- package/cli/agents/validator-epic-cloud.json +93 -0
- package/cli/agents/validator-epic-cloud.md +137 -0
- package/cli/agents/validator-epic-data.json +93 -0
- package/cli/agents/validator-epic-data.md +130 -0
- package/cli/agents/validator-epic-database.json +93 -0
- package/cli/agents/validator-epic-database.md +137 -0
- package/cli/agents/validator-epic-developer.json +74 -0
- package/cli/agents/validator-epic-developer.md +153 -0
- package/cli/agents/validator-epic-devops.json +74 -0
- package/cli/agents/validator-epic-devops.md +153 -0
- package/cli/agents/validator-epic-frontend.json +74 -0
- package/cli/agents/validator-epic-frontend.md +153 -0
- package/cli/agents/validator-epic-mobile.json +93 -0
- package/cli/agents/validator-epic-mobile.md +130 -0
- package/cli/agents/validator-epic-qa.json +93 -0
- package/cli/agents/validator-epic-qa.md +130 -0
- package/cli/agents/validator-epic-security.json +74 -0
- package/cli/agents/validator-epic-security.md +154 -0
- package/cli/agents/validator-epic-solution-architect.json +74 -0
- package/cli/agents/validator-epic-solution-architect.md +156 -0
- package/cli/agents/validator-epic-test-architect.json +93 -0
- package/cli/agents/validator-epic-test-architect.md +130 -0
- package/cli/agents/validator-epic-ui.json +93 -0
- package/cli/agents/validator-epic-ui.md +130 -0
- package/cli/agents/validator-epic-ux.json +93 -0
- package/cli/agents/validator-epic-ux.md +130 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/agents/validator-story-api.json +104 -0
- package/cli/agents/validator-story-api.md +152 -0
- package/cli/agents/validator-story-backend.json +104 -0
- package/cli/agents/validator-story-backend.md +152 -0
- package/cli/agents/validator-story-cloud.json +104 -0
- package/cli/agents/validator-story-cloud.md +152 -0
- package/cli/agents/validator-story-data.json +104 -0
- package/cli/agents/validator-story-data.md +152 -0
- package/cli/agents/validator-story-database.json +104 -0
- package/cli/agents/validator-story-database.md +152 -0
- package/cli/agents/validator-story-developer.json +104 -0
- package/cli/agents/validator-story-developer.md +152 -0
- package/cli/agents/validator-story-devops.json +104 -0
- package/cli/agents/validator-story-devops.md +152 -0
- package/cli/agents/validator-story-frontend.json +104 -0
- package/cli/agents/validator-story-frontend.md +152 -0
- package/cli/agents/validator-story-mobile.json +104 -0
- package/cli/agents/validator-story-mobile.md +152 -0
- package/cli/agents/validator-story-qa.json +104 -0
- package/cli/agents/validator-story-qa.md +152 -0
- package/cli/agents/validator-story-security.json +104 -0
- package/cli/agents/validator-story-security.md +152 -0
- package/cli/agents/validator-story-solution-architect.json +104 -0
- package/cli/agents/validator-story-solution-architect.md +152 -0
- package/cli/agents/validator-story-test-architect.json +104 -0
- package/cli/agents/validator-story-test-architect.md +152 -0
- package/cli/agents/validator-story-ui.json +104 -0
- package/cli/agents/validator-story-ui.md +152 -0
- package/cli/agents/validator-story-ux.json +104 -0
- package/cli/agents/validator-story-ux.md +152 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -0
- package/cli/command-logger.js +49 -12
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +1174 -0
- package/cli/evaluation-prompts.js +1008 -0
- package/cli/execution-context.js +195 -0
- package/cli/generate-summary-table.js +340 -0
- package/cli/index.js +0 -0
- package/cli/init-model-config.js +697 -0
- package/cli/init.js +1311 -274
- package/cli/kanban-server-manager.js +228 -0
- package/cli/llm-claude.js +83 -1
- package/cli/llm-gemini.js +85 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +233 -0
- package/cli/llm-provider.js +240 -3
- package/cli/llm-token-limits.js +102 -0
- package/cli/llm-verifier.js +454 -0
- package/cli/message-constants.js +58 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +297 -0
- package/cli/model-pricing.js +169 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +269 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/repl-ink.js +4988 -1217
- package/cli/repl-old.js +4 -4
- package/cli/seed-processor.js +792 -0
- package/cli/sprint-planning-processor.js +1813 -0
- package/cli/template-processor.js +2102 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +520 -0
- package/cli/tools/generate-story-validators.js +317 -0
- package/cli/tools/generate-validators.js +669 -0
- package/cli/update-checker.js +19 -17
- package/cli/update-notifier.js +4 -4
- package/cli/validation-router.js +605 -0
- package/cli/verification-tracker.js +563 -0
- package/kanban/README.md +386 -0
- package/kanban/client/README.md +205 -0
- package/kanban/client/components.json +20 -0
- package/kanban/client/dist/assets/index-CiD8PS2e.js +306 -0
- package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -0
- package/kanban/client/dist/index.html +16 -0
- package/kanban/client/dist/vite.svg +1 -0
- package/kanban/client/index.html +15 -0
- package/kanban/client/package-lock.json +9442 -0
- package/kanban/client/package.json +44 -0
- package/kanban/client/postcss.config.js +6 -0
- package/kanban/client/public/vite.svg +1 -0
- package/kanban/client/src/App.jsx +622 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -0
- package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
- package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
- package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +125 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -0
- package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
- package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +57 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -0
- package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
- package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
- package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +353 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -0
- package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
- package/kanban/client/src/components/stats/CostModal.jsx +353 -0
- package/kanban/client/src/components/ui/badge.jsx +27 -0
- package/kanban/client/src/components/ui/dialog.jsx +121 -0
- package/kanban/client/src/components/ui/tabs.jsx +85 -0
- package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
- package/kanban/client/src/hooks/useGrouping.js +118 -0
- package/kanban/client/src/hooks/useWebSocket.js +120 -0
- package/kanban/client/src/lib/__tests__/api.test.js +196 -0
- package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
- package/kanban/client/src/lib/api.js +401 -0
- package/kanban/client/src/lib/status-grouping.js +144 -0
- package/kanban/client/src/lib/utils.js +11 -0
- package/kanban/client/src/main.jsx +10 -0
- package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
- package/kanban/client/src/store/ceremonyStore.js +172 -0
- package/kanban/client/src/store/filterStore.js +201 -0
- package/kanban/client/src/store/kanbanStore.js +115 -0
- package/kanban/client/src/store/processStore.js +65 -0
- package/kanban/client/src/store/sprintPlanningStore.js +33 -0
- package/kanban/client/src/styles/globals.css +59 -0
- package/kanban/client/tailwind.config.js +77 -0
- package/kanban/client/vite.config.js +28 -0
- package/kanban/client/vitest.config.js +28 -0
- package/kanban/dev-start.sh +47 -0
- package/kanban/package.json +12 -0
- package/kanban/server/index.js +516 -0
- package/kanban/server/routes/ceremony.js +305 -0
- package/kanban/server/routes/costs.js +157 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +303 -0
- package/kanban/server/routes/websocket.js +276 -0
- package/kanban/server/routes/work-items.js +347 -0
- package/kanban/server/services/CeremonyService.js +1190 -0
- package/kanban/server/services/FileSystemScanner.js +95 -0
- package/kanban/server/services/FileWatcher.js +144 -0
- package/kanban/server/services/HierarchyBuilder.js +196 -0
- package/kanban/server/services/ProcessRegistry.js +122 -0
- package/kanban/server/services/WorkItemReader.js +123 -0
- package/kanban/server/services/WorkItemRefineService.js +510 -0
- package/kanban/server/start.js +49 -0
- package/kanban/server/utils/kanban-logger.js +132 -0
- package/kanban/server/utils/markdown.js +91 -0
- package/kanban/server/utils/status-grouping.js +107 -0
- package/kanban/server/workers/sponsor-call-worker.js +84 -0
- package/kanban/server/workers/sprint-planning-worker.js +130 -0
- package/package.json +18 -5
- package/cli/agents/documentation.md +0 -302
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client for AVC Kanban Board
|
|
3
|
+
* Communicates with the backend Express server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fetch wrapper with error handling
|
|
10
|
+
* @param {string} endpoint - API endpoint (relative to /api)
|
|
11
|
+
* @param {object} options - Fetch options
|
|
12
|
+
* @returns {Promise<any>} Response data
|
|
13
|
+
*/
|
|
14
|
+
async function apiFetch(endpoint, options = {}) {
|
|
15
|
+
const url = `${API_BASE_URL}${endpoint}`;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(url, {
|
|
19
|
+
...options,
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
...options.headers,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const error = await response.json().catch(() => ({}));
|
|
28
|
+
throw new Error(error.error || `HTTP ${response.status}: ${response.statusText}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return await response.json();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(`API Error [${endpoint}]:`, error);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get health check status
|
|
40
|
+
* @returns {Promise<object>} Health status
|
|
41
|
+
*/
|
|
42
|
+
export async function getHealth() {
|
|
43
|
+
return apiFetch('/health');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get statistics (counts by status/type)
|
|
48
|
+
* @returns {Promise<object>} Statistics
|
|
49
|
+
*/
|
|
50
|
+
export async function getStats() {
|
|
51
|
+
return apiFetch('/stats');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get all work items
|
|
56
|
+
* @param {object} filters - Filter options
|
|
57
|
+
* @param {string} filters.type - Filter by type (comma-separated: epic,story,task,subtask)
|
|
58
|
+
* @param {string} filters.status - Filter by status (comma-separated)
|
|
59
|
+
* @param {string} filters.search - Search query
|
|
60
|
+
* @returns {Promise<object>} Work items data
|
|
61
|
+
*/
|
|
62
|
+
export async function getWorkItems(filters = {}) {
|
|
63
|
+
const params = new URLSearchParams();
|
|
64
|
+
|
|
65
|
+
if (filters.type) {
|
|
66
|
+
params.append('type', filters.type);
|
|
67
|
+
}
|
|
68
|
+
if (filters.status) {
|
|
69
|
+
params.append('status', filters.status);
|
|
70
|
+
}
|
|
71
|
+
if (filters.search) {
|
|
72
|
+
params.append('search', filters.search);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const query = params.toString();
|
|
76
|
+
const endpoint = query ? `/work-items?${query}` : '/work-items';
|
|
77
|
+
|
|
78
|
+
return apiFetch(endpoint);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get work items grouped by column
|
|
83
|
+
* @returns {Promise<object>} Grouped work items
|
|
84
|
+
*/
|
|
85
|
+
export async function getWorkItemsGrouped() {
|
|
86
|
+
return apiFetch('/work-items/grouped');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get single work item with full details
|
|
91
|
+
* @param {string} id - Work item ID
|
|
92
|
+
* @returns {Promise<object>} Work item details
|
|
93
|
+
*/
|
|
94
|
+
export async function getWorkItem(id) {
|
|
95
|
+
return apiFetch(`/work-items/${id}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get rendered documentation (doc.md) as HTML
|
|
100
|
+
* @param {string} id - Work item ID
|
|
101
|
+
* @returns {Promise<string>} HTML content or empty string if not found
|
|
102
|
+
*/
|
|
103
|
+
export async function getWorkItemDoc(id) {
|
|
104
|
+
const url = `${API_BASE_URL}/work-items/${id}/doc`;
|
|
105
|
+
const response = await fetch(url);
|
|
106
|
+
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
return ''; // Return empty string on error (404, etc.)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return await response.text();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get raw markdown source of doc.md
|
|
116
|
+
* @param {string} id - Work item ID
|
|
117
|
+
* @returns {Promise<string>} Raw markdown or empty string if not found
|
|
118
|
+
*/
|
|
119
|
+
export async function getWorkItemDocRaw(id) {
|
|
120
|
+
const url = `${API_BASE_URL}/work-items/${id}/doc/raw`;
|
|
121
|
+
const response = await fetch(url);
|
|
122
|
+
if (!response.ok) return '';
|
|
123
|
+
return await response.text();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Save updated markdown to doc.md
|
|
128
|
+
* @param {string} id - Work item ID
|
|
129
|
+
* @param {string} content - Markdown content
|
|
130
|
+
*/
|
|
131
|
+
export async function updateWorkItemDoc(id, content) {
|
|
132
|
+
return apiFetch(`/work-items/${id}/doc`, {
|
|
133
|
+
method: 'PUT',
|
|
134
|
+
body: JSON.stringify({ content }),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ββ Board settings βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get board title from avc.json (falls back to 'AVC Kanban Board')
|
|
142
|
+
*/
|
|
143
|
+
export async function getBoardTitle() {
|
|
144
|
+
const data = await apiFetch('/settings/title');
|
|
145
|
+
return data.title || 'AVC Kanban Board';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function getDocsUrl() {
|
|
149
|
+
const data = await apiFetch('/settings/docs-url');
|
|
150
|
+
return data.url || 'http://localhost:4173';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Save board title to avc.json
|
|
155
|
+
*/
|
|
156
|
+
export async function updateBoardTitle(title) {
|
|
157
|
+
return apiFetch('/settings/title', {
|
|
158
|
+
method: 'PUT',
|
|
159
|
+
body: JSON.stringify({ title }),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ββ Project-level (root) files ββββββββββββββββββββββββββββββββββββββββββββββ
|
|
164
|
+
|
|
165
|
+
export async function getProjectDoc() {
|
|
166
|
+
const url = `${API_BASE_URL}/project/doc`;
|
|
167
|
+
const response = await fetch(url);
|
|
168
|
+
if (!response.ok) return '';
|
|
169
|
+
return response.text();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function getProjectDocRaw() {
|
|
173
|
+
const url = `${API_BASE_URL}/project/doc/raw`;
|
|
174
|
+
const response = await fetch(url);
|
|
175
|
+
if (!response.ok) return '';
|
|
176
|
+
return response.text();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function updateProjectDoc(content) {
|
|
180
|
+
return apiFetch('/project/doc', { method: 'PUT', body: JSON.stringify({ content }) });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function getProjectStatus() {
|
|
184
|
+
return apiFetch('/project/status');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ββ Settings API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
188
|
+
|
|
189
|
+
export async function getSettings() {
|
|
190
|
+
return apiFetch('/settings');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function saveApiKeys(keys) {
|
|
194
|
+
return apiFetch('/settings/api-keys', { method: 'PUT', body: JSON.stringify(keys) });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function saveCeremonies(ceremonies, missionGenerator) {
|
|
198
|
+
return apiFetch('/settings/ceremonies', { method: 'PUT', body: JSON.stringify({ ceremonies, missionGenerator }) });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function saveGeneralSettings(data) {
|
|
202
|
+
return apiFetch('/settings/general', { method: 'PUT', body: JSON.stringify(data) });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function saveModelPricing(models) {
|
|
206
|
+
return apiFetch('/settings/models', { method: 'PUT', body: JSON.stringify({ models }) });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function saveCostThresholds(thresholds) {
|
|
210
|
+
return apiFetch('/settings/cost-thresholds', { method: 'PUT', body: JSON.stringify({ thresholds }) });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ββ Ceremony API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
214
|
+
|
|
215
|
+
export async function getCeremonyStatus() {
|
|
216
|
+
return apiFetch('/ceremony/status');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function analyzeDatabase(mission, scope, strategy) {
|
|
220
|
+
return apiFetch('/ceremony/analyze/database', {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
body: JSON.stringify({ mission, scope, strategy }),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function analyzeArchitecture(mission, scope, dbContext, strategy) {
|
|
227
|
+
return apiFetch('/ceremony/analyze/architecture', {
|
|
228
|
+
method: 'POST',
|
|
229
|
+
body: JSON.stringify({ mission, scope, dbContext, strategy }),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function prefillAnswers(mission, scope, arch, dbContext, strategy) {
|
|
234
|
+
return apiFetch('/ceremony/analyze/prefill', {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
body: JSON.stringify({ mission, scope, arch, dbContext, strategy }),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export async function runCeremony(requirements) {
|
|
241
|
+
return apiFetch('/ceremony/run', {
|
|
242
|
+
method: 'POST',
|
|
243
|
+
body: JSON.stringify({ requirements }),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function runSprintPlanning() {
|
|
248
|
+
return apiFetch('/ceremony/sprint-planning/run', { method: 'POST' });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const confirmSprintPlanningSelection = (selectedEpicIds, selectedStoryIds) =>
|
|
252
|
+
apiFetch('/ceremony/sprint-planning/confirm-selection', {
|
|
253
|
+
method: 'POST',
|
|
254
|
+
body: JSON.stringify({ selectedEpicIds, selectedStoryIds }),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
export const pauseCeremony = () => apiFetch('/ceremony/pause', { method: 'POST' });
|
|
258
|
+
export const resumeCeremony = () => apiFetch('/ceremony/resume', { method: 'POST' });
|
|
259
|
+
export const cancelCeremony = () => apiFetch('/ceremony/cancel', { method: 'POST' });
|
|
260
|
+
export const resetCeremony = () => apiFetch('/ceremony/reset', { method: 'POST' });
|
|
261
|
+
export const continuePastCostLimit = () => apiFetch('/ceremony/cost-limit-continue', { method: 'POST' });
|
|
262
|
+
|
|
263
|
+
export async function getModels() {
|
|
264
|
+
return apiFetch('/ceremony/models');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function generateMission(description, modelId, provider, validatorModelId, validatorProvider) {
|
|
268
|
+
return apiFetch('/ceremony/generate-mission', {
|
|
269
|
+
method: 'POST',
|
|
270
|
+
body: JSON.stringify({ description, modelId, provider, validatorModelId, validatorProvider }),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function refineMission(missionStatement, initialScope, refinementRequest, modelId, provider, validatorModelId, validatorProvider) {
|
|
275
|
+
return apiFetch('/ceremony/refine-mission', {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
body: JSON.stringify({ missionStatement, initialScope, refinementRequest, modelId, provider, validatorModelId, validatorProvider }),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export async function generateArchitecture(description, modelId, provider) {
|
|
282
|
+
return apiFetch('/ceremony/generate-architecture', {
|
|
283
|
+
method: 'POST',
|
|
284
|
+
body: JSON.stringify({ description, modelId, provider }),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export async function refineArchitecture(currentArch, refinementRequest, modelId, provider) {
|
|
289
|
+
return apiFetch('/ceremony/refine-architecture', {
|
|
290
|
+
method: 'POST',
|
|
291
|
+
body: JSON.stringify({ currentArch, refinementRequest, modelId, provider }),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ββ Sponsor-call draft (resume support) ββββββββββββββββββββββββββββββββββββββ
|
|
296
|
+
|
|
297
|
+
export async function getSponsorCallDraft() {
|
|
298
|
+
try {
|
|
299
|
+
return await apiFetch('/ceremony/sponsor-call/draft');
|
|
300
|
+
} catch (_) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export async function saveSponsorCallDraft(data) {
|
|
306
|
+
return apiFetch('/ceremony/sponsor-call/draft', {
|
|
307
|
+
method: 'PUT',
|
|
308
|
+
body: JSON.stringify(data),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export async function deleteSponsorCallDraft() {
|
|
313
|
+
try {
|
|
314
|
+
return await apiFetch('/ceremony/sponsor-call/draft', { method: 'DELETE' });
|
|
315
|
+
} catch (_) {}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ββ Agents API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
319
|
+
|
|
320
|
+
export async function getAgentList() {
|
|
321
|
+
return apiFetch('/settings/agents');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function getAgentContent(name) {
|
|
325
|
+
return apiFetch(`/settings/agents/${encodeURIComponent(name)}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export async function saveAgentContent(name, content) {
|
|
329
|
+
return apiFetch(`/settings/agents/${encodeURIComponent(name)}`, {
|
|
330
|
+
method: 'PUT',
|
|
331
|
+
body: JSON.stringify({ content }),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export async function resetAgent(name) {
|
|
336
|
+
return apiFetch(`/settings/agents/${encodeURIComponent(name)}`, { method: 'DELETE' });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ββ Processes API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
340
|
+
|
|
341
|
+
export async function getProcesses() {
|
|
342
|
+
return apiFetch('/processes');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export async function killProcess(id) {
|
|
346
|
+
return apiFetch(`/processes/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export async function clearCompletedProcesses() {
|
|
350
|
+
return apiFetch('/processes', { method: 'DELETE' });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ββ Work Item Refine API ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Start an async refinement job for an epic or story.
|
|
357
|
+
* @param {string} id - Work item ID
|
|
358
|
+
* @param {object} options - { refinementRequest, selectedIssues, modelId, provider, validatorModelId, validatorProvider }
|
|
359
|
+
* @returns {Promise<{ jobId: string }>}
|
|
360
|
+
*/
|
|
361
|
+
export async function refineWorkItem(id, options) {
|
|
362
|
+
return apiFetch(`/work-items/${id}/refine`, {
|
|
363
|
+
method: 'POST',
|
|
364
|
+
body: JSON.stringify(options),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Apply accepted refinement changes to work.json on disk.
|
|
370
|
+
* @param {string} id - Work item ID
|
|
371
|
+
* @param {object} proposedItem - The proposed (refined) item
|
|
372
|
+
* @param {Array} storyChanges - [{ type: 'update'|'new', storyId?, proposedStory }]
|
|
373
|
+
*/
|
|
374
|
+
export async function applyWorkItemChanges(id, proposedItem, storyChanges = []) {
|
|
375
|
+
return apiFetch(`/work-items/${id}`, {
|
|
376
|
+
method: 'PUT',
|
|
377
|
+
body: JSON.stringify({ proposedItem, storyChanges }),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ββ Costs API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get current month cost summary for the header chip
|
|
385
|
+
* @returns {Promise<{ totalCost: number, totalTokens: number, apiCalls: number }>}
|
|
386
|
+
*/
|
|
387
|
+
export async function getCostSummary() {
|
|
388
|
+
return apiFetch('/costs/summary');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get cost history for chart and ceremony breakdown
|
|
393
|
+
* @param {number|{ from: string, to: string }} range - Days count or custom date range
|
|
394
|
+
* @returns {Promise<{ daily: Array, ceremonies: Array }>}
|
|
395
|
+
*/
|
|
396
|
+
export async function getCostHistory(range = 30) {
|
|
397
|
+
if (typeof range === 'number') {
|
|
398
|
+
return apiFetch(`/costs/history?days=${range}`);
|
|
399
|
+
}
|
|
400
|
+
return apiFetch(`/costs/history?from=${range.from}&to=${range.to}`);
|
|
401
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Column Grouping Logic (Frontend)
|
|
3
|
+
* Mirrors backend logic for consistency
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Maps status values to their display column
|
|
8
|
+
*/
|
|
9
|
+
export const STATUS_COLUMN_MAPPING = {
|
|
10
|
+
Backlog: ['planned', 'pending'],
|
|
11
|
+
Ready: ['ready'],
|
|
12
|
+
'In Progress': ['implementing', 'feedback'],
|
|
13
|
+
Review: ['implemented', 'testing'],
|
|
14
|
+
Done: ['completed'],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Metadata for each status value
|
|
19
|
+
*/
|
|
20
|
+
export const STATUS_METADATA = {
|
|
21
|
+
planned: { color: 'gray', icon: 'π', label: 'Planned' },
|
|
22
|
+
pending: { color: 'slate', icon: 'βΈοΈ', label: 'Pending' },
|
|
23
|
+
ready: { color: 'blue', icon: 'π', label: 'Ready' },
|
|
24
|
+
implementing: { color: 'yellow', icon: 'βοΈ', label: 'Implementing' },
|
|
25
|
+
feedback: { color: 'amber', icon: 'π¬', label: 'Feedback' },
|
|
26
|
+
implemented: { color: 'purple', icon: 'β
', label: 'Implemented' },
|
|
27
|
+
testing: { color: 'violet', icon: 'π§ͺ', label: 'Testing' },
|
|
28
|
+
completed: { color: 'green', icon: 'π', label: 'Completed' },
|
|
29
|
+
blocked: { color: 'red', icon: 'π«', label: 'Blocked' },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Canonical order for columns on the board
|
|
34
|
+
*/
|
|
35
|
+
export const COLUMN_ORDER = ['Backlog', 'Ready', 'In Progress', 'Review', 'Done'];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Column metadata
|
|
39
|
+
*/
|
|
40
|
+
export const COLUMN_METADATA = {
|
|
41
|
+
Backlog: {
|
|
42
|
+
color: 'slate',
|
|
43
|
+
bgColor: 'bg-slate-100',
|
|
44
|
+
borderColor: 'border-slate-300',
|
|
45
|
+
},
|
|
46
|
+
Ready: {
|
|
47
|
+
color: 'blue',
|
|
48
|
+
bgColor: 'bg-blue-100',
|
|
49
|
+
borderColor: 'border-blue-300',
|
|
50
|
+
},
|
|
51
|
+
'In Progress': {
|
|
52
|
+
color: 'yellow',
|
|
53
|
+
bgColor: 'bg-yellow-100',
|
|
54
|
+
borderColor: 'border-yellow-300',
|
|
55
|
+
},
|
|
56
|
+
Review: {
|
|
57
|
+
color: 'purple',
|
|
58
|
+
bgColor: 'bg-purple-100',
|
|
59
|
+
borderColor: 'border-purple-300',
|
|
60
|
+
},
|
|
61
|
+
Done: {
|
|
62
|
+
color: 'green',
|
|
63
|
+
bgColor: 'bg-green-100',
|
|
64
|
+
borderColor: 'border-green-300',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the column name for a given status
|
|
70
|
+
* @param {string} status - Work item status
|
|
71
|
+
* @returns {string|null} Column name or null if not found
|
|
72
|
+
*/
|
|
73
|
+
export function getColumnForStatus(status) {
|
|
74
|
+
for (const [column, statuses] of Object.entries(STATUS_COLUMN_MAPPING)) {
|
|
75
|
+
if (statuses.includes(status)) {
|
|
76
|
+
return column;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get metadata for a status
|
|
84
|
+
* @param {string} status - Work item status
|
|
85
|
+
* @returns {object|null} Status metadata or null if not found
|
|
86
|
+
*/
|
|
87
|
+
export function getStatusMetadata(status) {
|
|
88
|
+
return STATUS_METADATA[status] || null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get metadata for a column
|
|
93
|
+
* @param {string} column - Column name
|
|
94
|
+
* @returns {object|null} Column metadata or null if not found
|
|
95
|
+
*/
|
|
96
|
+
export function getColumnMetadata(column) {
|
|
97
|
+
return COLUMN_METADATA[column] || null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Group work items by column
|
|
102
|
+
* @param {Array} workItems - Array of work items
|
|
103
|
+
* @returns {object} Object with column names as keys, arrays of work items as values
|
|
104
|
+
*/
|
|
105
|
+
export function groupItemsByColumn(workItems) {
|
|
106
|
+
const grouped = {};
|
|
107
|
+
|
|
108
|
+
// Initialize all columns as empty arrays
|
|
109
|
+
COLUMN_ORDER.forEach((column) => {
|
|
110
|
+
grouped[column] = [];
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Group items by their column
|
|
114
|
+
workItems.forEach((item) => {
|
|
115
|
+
const column = getColumnForStatus(item.status);
|
|
116
|
+
if (column && grouped[column]) {
|
|
117
|
+
grouped[column].push(item);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return grouped;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get statistics for a column
|
|
126
|
+
* @param {Array} workItems - Work items in the column
|
|
127
|
+
* @returns {object} Statistics object with total count and breakdown by status
|
|
128
|
+
*/
|
|
129
|
+
export function getColumnStats(workItems) {
|
|
130
|
+
const stats = {
|
|
131
|
+
total: workItems.length,
|
|
132
|
+
byStatus: {},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
workItems.forEach((item) => {
|
|
136
|
+
const status = item.status;
|
|
137
|
+
if (!stats.byStatus[status]) {
|
|
138
|
+
stats.byStatus[status] = 0;
|
|
139
|
+
}
|
|
140
|
+
stats.byStatus[status]++;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return stats;
|
|
144
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Utility function to merge Tailwind CSS classes
|
|
6
|
+
* @param {...any} inputs - Class names to merge
|
|
7
|
+
* @returns {string} Merged class names
|
|
8
|
+
*/
|
|
9
|
+
export function cn(...inputs) {
|
|
10
|
+
return twMerge(clsx(inputs));
|
|
11
|
+
}
|