@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.
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +152 -0
- package/cli/agents/architecture-recommender.md +418 -0
- package/cli/agents/code-implementer.md +117 -0
- package/cli/agents/code-validator.md +80 -0
- package/cli/agents/context-reviewer-epic.md +101 -0
- package/cli/agents/context-reviewer-story.md +92 -0
- package/cli/agents/context-writer-epic.md +145 -0
- package/cli/agents/context-writer-story.md +111 -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/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +559 -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 +143 -0
- package/cli/agents/mission-scope-validator.md +146 -0
- package/cli/agents/project-context-extractor.md +122 -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/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -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 +183 -0
- package/cli/agents/validator-documentation.md +455 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/api-reference-tool.js +368 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -0
- package/cli/checks/catalog.json +76 -0
- package/cli/checks/code/quality.json +26 -0
- package/cli/checks/code/testing.json +14 -0
- package/cli/checks/code/traceability.json +26 -0
- package/cli/checks/cross-refs/epic.json +171 -0
- package/cli/checks/cross-refs/story.json +149 -0
- package/cli/checks/epic/api.json +114 -0
- package/cli/checks/epic/backend.json +126 -0
- package/cli/checks/epic/cloud.json +126 -0
- package/cli/checks/epic/data.json +102 -0
- package/cli/checks/epic/database.json +114 -0
- package/cli/checks/epic/developer.json +182 -0
- package/cli/checks/epic/devops.json +174 -0
- package/cli/checks/epic/frontend.json +162 -0
- package/cli/checks/epic/mobile.json +102 -0
- package/cli/checks/epic/qa.json +90 -0
- package/cli/checks/epic/security.json +184 -0
- package/cli/checks/epic/solution-architect.json +192 -0
- package/cli/checks/epic/test-architect.json +90 -0
- package/cli/checks/epic/ui.json +102 -0
- package/cli/checks/epic/ux.json +90 -0
- package/cli/checks/fixes/epic-fix-template.md +10 -0
- package/cli/checks/fixes/story-fix-template.md +10 -0
- package/cli/checks/story/api.json +186 -0
- package/cli/checks/story/backend.json +102 -0
- package/cli/checks/story/cloud.json +102 -0
- package/cli/checks/story/data.json +210 -0
- package/cli/checks/story/database.json +102 -0
- package/cli/checks/story/developer.json +168 -0
- package/cli/checks/story/devops.json +102 -0
- package/cli/checks/story/frontend.json +174 -0
- package/cli/checks/story/mobile.json +102 -0
- package/cli/checks/story/qa.json +210 -0
- package/cli/checks/story/security.json +198 -0
- package/cli/checks/story/solution-architect.json +230 -0
- package/cli/checks/story/test-architect.json +210 -0
- package/cli/checks/story/ui.json +102 -0
- package/cli/checks/story/ux.json +102 -0
- package/cli/coding-order.js +401 -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/dependency-checker.js +72 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +659 -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/init-model-config.js +704 -0
- package/cli/init.js +1737 -278
- package/cli/kanban-server-manager.js +227 -0
- package/cli/llm-claude.js +150 -1
- package/cli/llm-gemini.js +109 -0
- package/cli/llm-local.js +493 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +454 -0
- package/cli/llm-provider.js +379 -3
- package/cli/llm-token-limits.js +211 -0
- package/cli/llm-verifier.js +662 -0
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +49 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +291 -0
- package/cli/micro-check-fixer.js +335 -0
- package/cli/micro-check-runner.js +449 -0
- package/cli/micro-check-scorer.js +148 -0
- package/cli/micro-check-validator.js +538 -0
- package/cli/model-pricing.js +192 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +270 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +4625 -1094
- package/cli/repl-old.js +3 -4
- package/cli/seed-processor.js +962 -0
- package/cli/sprint-planning-processor.js +4162 -0
- package/cli/template-processor.js +2149 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +547 -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 +667 -0
- package/cli/verification-tracker.js +563 -0
- package/cli/worktree-runner.js +654 -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-D_KC5EQT.css +1 -0
- package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -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 +651 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -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 +329 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -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 +63 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
- package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
- package/kanban/client/src/components/kanban/SeedButton.jsx +176 -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 +381 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -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 +384 -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 +177 -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 +515 -0
- package/kanban/client/src/lib/status-grouping.js +154 -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 +123 -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 +537 -0
- package/kanban/server/routes/ceremony.js +454 -0
- package/kanban/server/routes/costs.js +163 -0
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +736 -0
- package/kanban/server/routes/websocket.js +281 -0
- package/kanban/server/routes/work-items.js +487 -0
- package/kanban/server/services/CeremonyService.js +1441 -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/TaskRunnerService.js +261 -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/run-task-worker.js +121 -0
- package/kanban/server/workers/seed-worker.js +94 -0
- package/kanban/server/workers/sponsor-call-worker.js +92 -0
- package/kanban/server/workers/sprint-planning-worker.js +212 -0
- package/package.json +19 -7
- package/cli/agents/documentation.md +0 -302
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import fsSync from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { renderMarkdown, extractDescriptionFromDoc } from '../utils/markdown.js';
|
|
6
|
+
import { groupItemsByColumn, getColumnStats } from '../utils/status-grouping.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create work items router
|
|
10
|
+
* @param {object} dataStore - Data store with work items
|
|
11
|
+
* @returns {express.Router}
|
|
12
|
+
*/
|
|
13
|
+
export function createWorkItemsRouter(dataStore, refineService, ceremonyService) {
|
|
14
|
+
const router = express.Router();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* GET /api/work-items
|
|
18
|
+
* Get all work items as hierarchical JSON
|
|
19
|
+
*/
|
|
20
|
+
router.get('/', (req, res) => {
|
|
21
|
+
try {
|
|
22
|
+
const { items, roots } = dataStore.getHierarchy();
|
|
23
|
+
|
|
24
|
+
// Convert Map to array
|
|
25
|
+
const allItems = Array.from(items.values());
|
|
26
|
+
|
|
27
|
+
// Apply filters if provided
|
|
28
|
+
let filtered = allItems;
|
|
29
|
+
|
|
30
|
+
// Filter by type
|
|
31
|
+
if (req.query.type) {
|
|
32
|
+
const types = req.query.type.split(',');
|
|
33
|
+
filtered = filtered.filter((item) => types.includes(item._type));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Filter by status
|
|
37
|
+
if (req.query.status) {
|
|
38
|
+
const statuses = req.query.status.split(',');
|
|
39
|
+
filtered = filtered.filter((item) => statuses.includes(item.status));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Search query
|
|
43
|
+
if (req.query.search) {
|
|
44
|
+
const search = req.query.search.toLowerCase();
|
|
45
|
+
filtered = filtered.filter(
|
|
46
|
+
(item) =>
|
|
47
|
+
item.name.toLowerCase().includes(search) ||
|
|
48
|
+
item.id.toLowerCase().includes(search) ||
|
|
49
|
+
(item.description && item.description.toLowerCase().includes(search))
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Clean items (remove circular references for JSON serialization)
|
|
54
|
+
const cleanItems = filtered.map((item) => cleanWorkItem(item));
|
|
55
|
+
|
|
56
|
+
res.json({
|
|
57
|
+
items: cleanItems,
|
|
58
|
+
total: cleanItems.length,
|
|
59
|
+
roots: roots.map((r) => r.id),
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Error getting work items:', error);
|
|
63
|
+
res.status(500).json({ error: 'Failed to get work items' });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* GET /api/work-items/grouped
|
|
69
|
+
* Get work items grouped by column
|
|
70
|
+
*/
|
|
71
|
+
router.get('/grouped', (req, res) => {
|
|
72
|
+
try {
|
|
73
|
+
const { items } = dataStore.getHierarchy();
|
|
74
|
+
const allItems = Array.from(items.values());
|
|
75
|
+
|
|
76
|
+
// Group by column
|
|
77
|
+
const grouped = groupItemsByColumn(allItems);
|
|
78
|
+
|
|
79
|
+
// Add statistics for each column
|
|
80
|
+
const result = {};
|
|
81
|
+
for (const [column, columnItems] of Object.entries(grouped)) {
|
|
82
|
+
result[column] = {
|
|
83
|
+
items: columnItems.map((item) => cleanWorkItem(item)),
|
|
84
|
+
stats: getColumnStats(columnItems),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
res.json(result);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('Error getting grouped work items:', error);
|
|
91
|
+
res.status(500).json({ error: 'Failed to get grouped work items' });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* GET /api/work-items/:id
|
|
97
|
+
* Get single work item with full details
|
|
98
|
+
*/
|
|
99
|
+
router.get('/:id', async (req, res) => {
|
|
100
|
+
try {
|
|
101
|
+
const { items } = dataStore.getHierarchy();
|
|
102
|
+
const item = items.get(req.params.id);
|
|
103
|
+
|
|
104
|
+
if (!item) {
|
|
105
|
+
return res.status(404).json({ error: 'Work item not found' });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Get full details (including doc.md and context.md)
|
|
109
|
+
const fullItem = await dataStore.getFullDetails(item);
|
|
110
|
+
|
|
111
|
+
res.json(cleanWorkItem(fullItem, true));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(`Error getting work item ${req.params.id}:`, error);
|
|
114
|
+
res.status(500).json({ error: 'Failed to get work item' });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* GET /api/work-items/:id/doc
|
|
120
|
+
* Get rendered documentation (doc.md) as HTML
|
|
121
|
+
*/
|
|
122
|
+
router.get('/:id/doc', async (req, res) => {
|
|
123
|
+
try {
|
|
124
|
+
const { items } = dataStore.getHierarchy();
|
|
125
|
+
const item = items.get(req.params.id);
|
|
126
|
+
|
|
127
|
+
if (!item) {
|
|
128
|
+
return res.status(404).json({ error: 'Work item not found' });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const fullItem = await dataStore.getFullDetails(item);
|
|
132
|
+
|
|
133
|
+
if (!fullItem.documentation) {
|
|
134
|
+
return res.status(404).json({ error: 'Documentation not found' });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const html = renderMarkdown(fullItem.documentation);
|
|
138
|
+
res.send(html);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(`Error getting documentation for ${req.params.id}:`, error);
|
|
141
|
+
res.status(500).json({ error: 'Failed to get documentation' });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* GET /api/work-items/:id/doc/raw
|
|
147
|
+
* Get raw markdown source of doc.md
|
|
148
|
+
*/
|
|
149
|
+
router.get('/:id/doc/raw', async (req, res) => {
|
|
150
|
+
try {
|
|
151
|
+
const { items } = dataStore.getHierarchy();
|
|
152
|
+
const item = items.get(req.params.id);
|
|
153
|
+
if (!item) return res.status(404).json({ error: 'Work item not found' });
|
|
154
|
+
|
|
155
|
+
const fullItem = await dataStore.getFullDetails(item);
|
|
156
|
+
if (!fullItem.documentation) return res.status(404).json({ error: 'Documentation not found' });
|
|
157
|
+
|
|
158
|
+
res.type('text/plain').send(fullItem.documentation);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(`Error getting raw doc for ${req.params.id}:`, error);
|
|
161
|
+
res.status(500).json({ error: 'Failed to get documentation' });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* POST /api/work-items/:id/refine
|
|
167
|
+
* Start an async refinement job for an epic or story.
|
|
168
|
+
* Body: { refinementRequest?, selectedIssues?, modelId, provider, validatorModelId, validatorProvider }
|
|
169
|
+
* Returns: { jobId }
|
|
170
|
+
*/
|
|
171
|
+
router.post('/:id/refine', async (req, res) => {
|
|
172
|
+
try {
|
|
173
|
+
if (!refineService) {
|
|
174
|
+
return res.status(503).json({ error: 'Refine service not available' });
|
|
175
|
+
}
|
|
176
|
+
const { items } = dataStore.getHierarchy();
|
|
177
|
+
const item = items.get(req.params.id);
|
|
178
|
+
if (!item) return res.status(404).json({ error: 'Work item not found' });
|
|
179
|
+
|
|
180
|
+
const { refinementRequest, selectedIssues, modelId, provider, validatorModelId, validatorProvider } =
|
|
181
|
+
req.body;
|
|
182
|
+
|
|
183
|
+
if (!modelId || !provider || !validatorModelId || !validatorProvider) {
|
|
184
|
+
return res.status(400).json({
|
|
185
|
+
error: 'modelId, provider, validatorModelId and validatorProvider are required',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const fullItem = await dataStore.getFullDetails(item);
|
|
190
|
+
const cleanItem = cleanWorkItem(fullItem, true);
|
|
191
|
+
|
|
192
|
+
const jobId = await refineService.startRefine(req.params.id, cleanItem, {
|
|
193
|
+
refinementRequest: refinementRequest || '',
|
|
194
|
+
selectedIssues: selectedIssues || [],
|
|
195
|
+
modelId,
|
|
196
|
+
provider,
|
|
197
|
+
validatorModelId,
|
|
198
|
+
validatorProvider,
|
|
199
|
+
itemDirPath: item._dirPath,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
res.json({ jobId });
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.error(`Error starting refine for ${req.params.id}:`, err);
|
|
205
|
+
res.status(500).json({ error: err.message });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* PUT /api/work-items/:id
|
|
211
|
+
* Apply accepted refinement changes to work.json on disk.
|
|
212
|
+
* Body: { proposedItem, storyChanges? }
|
|
213
|
+
*/
|
|
214
|
+
router.put('/:id', async (req, res) => {
|
|
215
|
+
try {
|
|
216
|
+
const cs = ceremonyService?.getStatus();
|
|
217
|
+
if (cs?.status === 'running' && cs?.runningType === 'sprint-planning') {
|
|
218
|
+
return res.status(423).json({ error: 'Work items are read-only while sprint planning is active' });
|
|
219
|
+
}
|
|
220
|
+
if (!refineService) {
|
|
221
|
+
return res.status(503).json({ error: 'Refine service not available' });
|
|
222
|
+
}
|
|
223
|
+
const { proposedItem, storyChanges } = req.body;
|
|
224
|
+
if (!proposedItem) return res.status(400).json({ error: 'proposedItem is required' });
|
|
225
|
+
|
|
226
|
+
// Build a dirPath map from the in-memory data store so applyChanges doesn't
|
|
227
|
+
// need to walk the filesystem (which can silently fail).
|
|
228
|
+
const { items } = dataStore.getHierarchy();
|
|
229
|
+
const item = items.get(req.params.id);
|
|
230
|
+
if (!item) return res.status(404).json({ error: 'Work item not found' });
|
|
231
|
+
|
|
232
|
+
const dirPathMap = new Map([[item.id, item._dirPath]]);
|
|
233
|
+
for (const change of (storyChanges || [])) {
|
|
234
|
+
if (change.storyId) {
|
|
235
|
+
const storyItem = items.get(change.storyId);
|
|
236
|
+
if (storyItem) dirPathMap.set(change.storyId, storyItem._dirPath);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
await refineService.applyChanges(req.params.id, proposedItem, storyChanges || [], dirPathMap);
|
|
241
|
+
res.json({ status: 'ok' });
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.error(`Error applying changes for ${req.params.id}:`, err);
|
|
244
|
+
res.status(500).json({ error: err.message });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* PUT /api/work-items/:id/doc
|
|
250
|
+
* Save updated markdown content to doc.md
|
|
251
|
+
*/
|
|
252
|
+
router.put('/:id/doc', async (req, res) => {
|
|
253
|
+
try {
|
|
254
|
+
const cs = ceremonyService?.getStatus();
|
|
255
|
+
if (cs?.status === 'running' && cs?.runningType === 'sprint-planning') {
|
|
256
|
+
return res.status(423).json({ error: 'Work items are read-only while sprint planning is active' });
|
|
257
|
+
}
|
|
258
|
+
const { items } = dataStore.getHierarchy();
|
|
259
|
+
const item = items.get(req.params.id);
|
|
260
|
+
if (!item) return res.status(404).json({ error: 'Work item not found' });
|
|
261
|
+
|
|
262
|
+
const markdown = req.body.content;
|
|
263
|
+
if (typeof markdown !== 'string') {
|
|
264
|
+
return res.status(400).json({ error: 'content must be a string' });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const docPath = path.join(item._dirPath, 'doc.md');
|
|
268
|
+
await fs.writeFile(docPath, markdown, 'utf8');
|
|
269
|
+
|
|
270
|
+
// Sync work.json description cache so kanban cards stay in sync
|
|
271
|
+
const newDescription = extractDescriptionFromDoc(markdown);
|
|
272
|
+
if (newDescription) {
|
|
273
|
+
const workJsonPath = path.join(item._dirPath, 'work.json');
|
|
274
|
+
if (fsSync.existsSync(workJsonPath)) {
|
|
275
|
+
const workJson = JSON.parse(fsSync.readFileSync(workJsonPath, 'utf8'));
|
|
276
|
+
workJson.description = newDescription;
|
|
277
|
+
fsSync.writeFileSync(workJsonPath, JSON.stringify(workJson, null, 2), 'utf8');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
res.json({ status: 'ok' });
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error(`Error saving doc for ${req.params.id}:`, error);
|
|
284
|
+
res.status(500).json({ error: 'Failed to save documentation' });
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// ---- Status update ----
|
|
289
|
+
const VALID_TRANSITIONS = {
|
|
290
|
+
'planned': ['ready', 'implementing'],
|
|
291
|
+
'ready': ['implementing'],
|
|
292
|
+
'implementing': ['completed', 'failed'],
|
|
293
|
+
'failed': ['implementing'],
|
|
294
|
+
'completed': [],
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
router.put('/:id/status', async (req, res) => {
|
|
298
|
+
try {
|
|
299
|
+
const cs = ceremonyService?.getStatus();
|
|
300
|
+
if (cs?.status === 'running' && cs?.runningType === 'sprint-planning') {
|
|
301
|
+
return res.status(423).json({ error: 'Work items are read-only while sprint planning is active' });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const { items } = dataStore.getHierarchy();
|
|
305
|
+
const item = items.get(req.params.id);
|
|
306
|
+
if (!item) return res.status(404).json({ error: 'Work item not found' });
|
|
307
|
+
|
|
308
|
+
const { status } = req.body;
|
|
309
|
+
if (!status || typeof status !== 'string') {
|
|
310
|
+
return res.status(400).json({ error: 'status is required' });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const currentStatus = item.status || 'planned';
|
|
314
|
+
const allowed = VALID_TRANSITIONS[currentStatus] || [];
|
|
315
|
+
if (!allowed.includes(status)) {
|
|
316
|
+
return res.status(422).json({
|
|
317
|
+
error: `Invalid status transition: ${currentStatus} → ${status}`,
|
|
318
|
+
allowed,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Write to work.json
|
|
323
|
+
const workJsonPath = path.join(item._dirPath, 'work.json');
|
|
324
|
+
if (fsSync.existsSync(workJsonPath)) {
|
|
325
|
+
const workJson = JSON.parse(fsSync.readFileSync(workJsonPath, 'utf8'));
|
|
326
|
+
workJson.status = status;
|
|
327
|
+
workJson.updated = new Date().toISOString();
|
|
328
|
+
fsSync.writeFileSync(workJsonPath, JSON.stringify(workJson, null, 2), 'utf8');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Reload hierarchy so in-memory state reflects the change
|
|
332
|
+
await dataStore.reload();
|
|
333
|
+
|
|
334
|
+
// When an item is completed, cascade: promote dependents to 'ready' if all their deps are now met
|
|
335
|
+
if (status === 'completed') {
|
|
336
|
+
try {
|
|
337
|
+
const { checkDependenciesReady } = await import('../../../cli/dependency-checker.js');
|
|
338
|
+
const { items: freshItems } = dataStore.getHierarchy();
|
|
339
|
+
|
|
340
|
+
// Build lookup from fresh hierarchy
|
|
341
|
+
const lookup = {};
|
|
342
|
+
for (const [id, wi] of freshItems) {
|
|
343
|
+
lookup[id] = {
|
|
344
|
+
id: wi.id, name: wi.name, type: wi._type || wi.type,
|
|
345
|
+
status: wi.status || 'planned', dependencies: wi.dependencies || [],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Find items that depend on the completed item and are still 'planned'
|
|
350
|
+
let promoted = 0;
|
|
351
|
+
for (const [id, wi] of freshItems) {
|
|
352
|
+
if (wi.status !== 'planned') continue;
|
|
353
|
+
const deps = wi.dependencies || [];
|
|
354
|
+
if (!deps.includes(req.params.id)) continue;
|
|
355
|
+
|
|
356
|
+
// Check if ALL dependencies are now met
|
|
357
|
+
const result = checkDependenciesReady(id, lookup);
|
|
358
|
+
if (result.ready) {
|
|
359
|
+
const depWorkJsonPath = path.join(wi._dirPath, 'work.json');
|
|
360
|
+
if (fsSync.existsSync(depWorkJsonPath)) {
|
|
361
|
+
const wj = JSON.parse(fsSync.readFileSync(depWorkJsonPath, 'utf8'));
|
|
362
|
+
wj.status = 'ready';
|
|
363
|
+
wj.updated = new Date().toISOString();
|
|
364
|
+
fsSync.writeFileSync(depWorkJsonPath, JSON.stringify(wj, null, 2), 'utf8');
|
|
365
|
+
promoted++;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (promoted > 0) {
|
|
371
|
+
await dataStore.reload();
|
|
372
|
+
console.log(`[status-cascade] ${promoted} item(s) promoted to 'ready' after ${req.params.id} completed`);
|
|
373
|
+
}
|
|
374
|
+
} catch (cascadeErr) {
|
|
375
|
+
console.error('Status cascade error (non-critical):', cascadeErr.message);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
res.json({ status: 'ok', item: cleanWorkItem(item) });
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.error(`Error updating status for ${req.params.id}:`, error);
|
|
382
|
+
res.status(500).json({ error: 'Failed to update status' });
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// ---- Dependency status check ----
|
|
387
|
+
router.get('/:id/dependency-status', (req, res) => {
|
|
388
|
+
try {
|
|
389
|
+
const { items } = dataStore.getHierarchy();
|
|
390
|
+
const item = items.get(req.params.id);
|
|
391
|
+
if (!item) return res.status(404).json({ error: 'Work item not found' });
|
|
392
|
+
|
|
393
|
+
// Build a plain lookup object from the items Map
|
|
394
|
+
const lookup = {};
|
|
395
|
+
for (const [id, wi] of items) {
|
|
396
|
+
lookup[id] = {
|
|
397
|
+
id: wi.id,
|
|
398
|
+
name: wi.name,
|
|
399
|
+
type: wi._type || wi.type,
|
|
400
|
+
status: wi.status || 'planned',
|
|
401
|
+
dependencies: wi.dependencies || [],
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Dynamic import to keep the route file sync-compatible
|
|
406
|
+
import('../../../cli/dependency-checker.js').then(({ checkDependenciesReady }) => {
|
|
407
|
+
const result = checkDependenciesReady(req.params.id, lookup);
|
|
408
|
+
res.json(result);
|
|
409
|
+
}).catch(err => {
|
|
410
|
+
console.error(`Error checking dependencies for ${req.params.id}:`, err);
|
|
411
|
+
res.status(500).json({ error: 'Failed to check dependencies' });
|
|
412
|
+
});
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.error(`Error checking dependencies for ${req.params.id}:`, error);
|
|
415
|
+
res.status(500).json({ error: 'Failed to check dependencies' });
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
return router;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Clean work item for JSON serialization
|
|
424
|
+
* Removes circular references and internal fields
|
|
425
|
+
* @param {object} item - Work item
|
|
426
|
+
* @param {boolean} includeFullDetails - Include documentation and context
|
|
427
|
+
* @returns {object} Cleaned work item
|
|
428
|
+
*/
|
|
429
|
+
function cleanWorkItem(item, includeFullDetails = false) {
|
|
430
|
+
const cleaned = {
|
|
431
|
+
id: item.id,
|
|
432
|
+
name: item.name,
|
|
433
|
+
type: item._type,
|
|
434
|
+
status: item.status,
|
|
435
|
+
description: item.description,
|
|
436
|
+
dependencies: item.dependencies || [],
|
|
437
|
+
metadata: item.metadata,
|
|
438
|
+
created: item.created,
|
|
439
|
+
updated: item.updated,
|
|
440
|
+
// Type-specific fields used for diff display in RefineWorkItemPopup
|
|
441
|
+
features: item.features, // epics
|
|
442
|
+
acceptance: item.acceptance, // stories
|
|
443
|
+
userType: item.userType, // stories
|
|
444
|
+
domain: item.domain, // epics
|
|
445
|
+
functions: item.functions, // code traceability registry
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// Add parent reference (ID only, not full object)
|
|
449
|
+
if (item._parentId) {
|
|
450
|
+
cleaned.parentId = item._parentId;
|
|
451
|
+
cleaned.parentName = item._parent?.name;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Add children references (IDs only)
|
|
455
|
+
if (item._children && item._children.length > 0) {
|
|
456
|
+
cleaned.children = item._children.map((child) => ({
|
|
457
|
+
id: child.id,
|
|
458
|
+
name: child.name,
|
|
459
|
+
type: child._type,
|
|
460
|
+
status: child.status,
|
|
461
|
+
}));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Add epic reference for nested items
|
|
465
|
+
if (item._type !== 'epic' && item._parent) {
|
|
466
|
+
let epic = item._parent;
|
|
467
|
+
while (epic._parent) {
|
|
468
|
+
epic = epic._parent;
|
|
469
|
+
}
|
|
470
|
+
if (epic._type === 'epic') {
|
|
471
|
+
cleaned.epicId = epic.id;
|
|
472
|
+
cleaned.epicName = epic.name;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Include full details if requested
|
|
477
|
+
if (includeFullDetails) {
|
|
478
|
+
if (item.documentation) {
|
|
479
|
+
cleaned.documentation = item.documentation;
|
|
480
|
+
}
|
|
481
|
+
if (item.context) {
|
|
482
|
+
cleaned.context = item.context;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return cleaned;
|
|
487
|
+
}
|