@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,537 @@
1
+ import express from 'express';
2
+ import http from 'http';
3
+ import cors from 'cors';
4
+ import path from 'path';
5
+ import fs from 'fs/promises';
6
+ import { fileURLToPath } from 'url';
7
+ import chokidar from 'chokidar';
8
+ import { FileSystemScanner } from './services/FileSystemScanner.js';
9
+ import { WorkItemReader } from './services/WorkItemReader.js';
10
+ import { HierarchyBuilder } from './services/HierarchyBuilder.js';
11
+ import { FileWatcher } from './services/FileWatcher.js';
12
+ import { createWorkItemsRouter } from './routes/work-items.js';
13
+ import { createCeremonyRouter } from './routes/ceremony.js';
14
+ import { createProcessesRouter } from './routes/processes.js';
15
+ import { createSettingsRouter } from './routes/settings.js';
16
+ import { createOpenAIOAuthRouter } from './routes/openai-oauth.js';
17
+ import { createCostsRouter } from './routes/costs.js';
18
+ import { setupWebSocket } from './routes/websocket.js';
19
+ import { renderMarkdown } from './utils/markdown.js';
20
+ import { CeremonyService } from './services/CeremonyService.js';
21
+ import { TaskRunnerService } from './services/TaskRunnerService.js';
22
+ import { ProcessRegistry } from './services/ProcessRegistry.js';
23
+ import { WorkItemRefineService } from './services/WorkItemRefineService.js';
24
+
25
+ /**
26
+ * KanbanServer
27
+ * Express server for AVC Kanban Board
28
+ */
29
+ export class KanbanServer {
30
+ /**
31
+ * @param {string} projectRoot - Absolute path to project root directory
32
+ * @param {object} options - Server options
33
+ */
34
+ constructor(projectRoot, options = {}) {
35
+ this.projectRoot = projectRoot;
36
+ this.port = options.port || 4174;
37
+ this.host = options.host || 'localhost';
38
+
39
+ // Path to pre-built React client
40
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
41
+ this.clientDistPath = path.join(__dirname, '..', 'client', 'dist');
42
+
43
+ // Services
44
+ this.scanner = new FileSystemScanner(projectRoot);
45
+ this.reader = new WorkItemReader(projectRoot);
46
+ this.hierarchyBuilder = new HierarchyBuilder();
47
+ this.fileWatcher = new FileWatcher(projectRoot);
48
+
49
+ // Data store
50
+ this.hierarchy = null;
51
+
52
+ // Ceremony service + process registry
53
+ this.ceremonyService = new CeremonyService(projectRoot);
54
+ this.processRegistry = new ProcessRegistry();
55
+
56
+ // Task runner service for seed + run-task operations
57
+ this.taskRunnerService = new TaskRunnerService(projectRoot);
58
+
59
+ // Work item refine service (websocket injected after start())
60
+ this.refineService = new WorkItemRefineService(projectRoot);
61
+
62
+ // Express app
63
+ this.app = express();
64
+ this.server = null;
65
+ this.websocket = null;
66
+
67
+ // Setup middleware
68
+ this.setupMiddleware();
69
+
70
+ // Setup routes
71
+ this.setupRoutes();
72
+ }
73
+
74
+ /**
75
+ * Setup Express middleware
76
+ */
77
+ setupMiddleware() {
78
+ // CORS for frontend development server
79
+ this.app.use(
80
+ cors({
81
+ origin: [`http://localhost:${this.port}`, 'http://localhost:5173', 'http://127.0.0.1:5173'],
82
+ credentials: true,
83
+ })
84
+ );
85
+
86
+ // JSON body parser
87
+ this.app.use(express.json());
88
+
89
+ // Serve pre-built React client
90
+ this.app.use(express.static(this.clientDistPath));
91
+
92
+ // Request logging
93
+ this.app.use((req, res, next) => {
94
+ console.log(`${req.method} ${req.path}`);
95
+ next();
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Setup API routes
101
+ */
102
+ setupRoutes() {
103
+ // Health check
104
+ this.app.get('/api/health', (req, res) => {
105
+ res.json({
106
+ status: 'ok',
107
+ timestamp: Date.now(),
108
+ projectRoot: this.projectRoot,
109
+ });
110
+ });
111
+
112
+ // Statistics
113
+ this.app.get('/api/stats', (req, res) => {
114
+ if (!this.hierarchy) {
115
+ return res.status(503).json({ error: 'Data not loaded yet' });
116
+ }
117
+
118
+ const { items } = this.hierarchy;
119
+ const allItems = Array.from(items.values());
120
+
121
+ const stats = {
122
+ total: allItems.length,
123
+ byType: {},
124
+ byStatus: {},
125
+ };
126
+
127
+ allItems.forEach((item) => {
128
+ // Count by type
129
+ const type = item._type;
130
+ stats.byType[type] = (stats.byType[type] || 0) + 1;
131
+
132
+ // Count by status
133
+ const status = item.status;
134
+ stats.byStatus[status] = (stats.byStatus[status] || 0) + 1;
135
+ });
136
+
137
+ res.json(stats);
138
+ });
139
+
140
+ // Manual reload endpoint
141
+ this.app.post('/api/reload', async (req, res) => {
142
+ try {
143
+ await this.reloadWorkItems();
144
+ const count = this.hierarchy ? this.hierarchy.items.size : 0;
145
+ res.json({ status: 'ok', workItemCount: count });
146
+ if (this.websocket) {
147
+ this.websocket.broadcastRefresh();
148
+ }
149
+ } catch (error) {
150
+ console.error('Error during manual reload:', error);
151
+ res.status(500).json({ error: 'Reload failed', message: error.message });
152
+ }
153
+ });
154
+
155
+ // Project-level doc.md and context.md (root .avc/project/ files)
156
+ const projectPath = path.join(this.projectRoot, '.avc', 'project');
157
+
158
+ const readProjectFile = async (filename) => {
159
+ try {
160
+ return await fs.readFile(path.join(projectPath, filename), 'utf8');
161
+ } catch {
162
+ return null;
163
+ }
164
+ };
165
+
166
+ this.app.get('/api/project/doc', async (req, res) => {
167
+ const md = await readProjectFile('doc.md');
168
+ if (!md) return res.status(404).json({ error: 'Project doc.md not found' });
169
+ res.send(renderMarkdown(md));
170
+ });
171
+
172
+ this.app.get('/api/project/doc/raw', async (req, res) => {
173
+ const md = await readProjectFile('doc.md');
174
+ if (!md) return res.status(404).json({ error: 'Project doc.md not found' });
175
+ res.type('text/plain').send(md);
176
+ });
177
+
178
+ this.app.put('/api/project/doc', async (req, res) => {
179
+ const { content } = req.body;
180
+ if (typeof content !== 'string') return res.status(400).json({ error: 'content must be a string' });
181
+ await fs.writeFile(path.join(projectPath, 'doc.md'), content, 'utf8');
182
+ res.json({ status: 'ok' });
183
+ });
184
+
185
+ this.app.get('/api/project/status', async (req, res) => {
186
+ const docExists = await fs.access(path.join(projectPath, 'doc.md')).then(() => true).catch(() => false);
187
+ res.json({ docExists });
188
+ });
189
+
190
+ // Settings router (GET /api/settings + PUT sub-routes)
191
+ const settingsRouter = createSettingsRouter(this.projectRoot);
192
+ this.app.use('/api/settings', settingsRouter);
193
+
194
+ // OpenAI OAuth router
195
+ const openaiOAuthRouter = createOpenAIOAuthRouter(
196
+ this.projectRoot,
197
+ () => this.websocket,
198
+ );
199
+ this.app.use('/api/settings/openai-oauth', openaiOAuthRouter);
200
+
201
+ // Board title setting (read/write from avc.json)
202
+ const avcJsonPath = path.join(this.projectRoot, '.avc', 'avc.json');
203
+ const DEFAULT_TITLE = 'AVC Kanban Board';
204
+
205
+ const readAvcConfig = async () => {
206
+ try {
207
+ return JSON.parse(await fs.readFile(avcJsonPath, 'utf8'));
208
+ } catch {
209
+ return {};
210
+ }
211
+ };
212
+
213
+ this.app.get('/api/settings/title', async (req, res) => {
214
+ const config = await readAvcConfig();
215
+ res.json({ title: config?.settings?.kanban?.title || DEFAULT_TITLE });
216
+ });
217
+
218
+ this.app.get('/api/settings/docs-url', async (req, res) => {
219
+ const config = await readAvcConfig();
220
+ const docsPort = config?.settings?.documentation?.port || 4173;
221
+ res.json({ url: `http://localhost:${docsPort}` });
222
+ });
223
+
224
+ this.app.put('/api/settings/title', async (req, res) => {
225
+ const { title } = req.body;
226
+ if (typeof title !== 'string' || !title.trim()) {
227
+ return res.status(400).json({ error: 'title must be a non-empty string' });
228
+ }
229
+ try {
230
+ const config = await readAvcConfig();
231
+ if (!config.settings) config.settings = {};
232
+ if (!config.settings.kanban) config.settings.kanban = {};
233
+ config.settings.kanban.title = title.trim();
234
+ await fs.writeFile(avcJsonPath, JSON.stringify(config, null, 2), 'utf8');
235
+ res.json({ title: title.trim() });
236
+ } catch (err) {
237
+ res.status(500).json({ error: err.message });
238
+ }
239
+ });
240
+
241
+ // Work items routes
242
+ const workItemsRouter = createWorkItemsRouter(this, this.refineService, this.ceremonyService);
243
+ this.app.use('/api/work-items', workItemsRouter);
244
+
245
+ // Ceremony routes
246
+ const ceremonyRouter = createCeremonyRouter(this.ceremonyService, this.processRegistry, this.taskRunnerService);
247
+ this.app.use('/api/ceremony', ceremonyRouter);
248
+
249
+ // Process monitor routes
250
+ const processesRouter = createProcessesRouter(this.processRegistry);
251
+ this.app.use('/api/processes', processesRouter);
252
+
253
+ // Costs routes
254
+ const costsRouter = createCostsRouter(this.projectRoot);
255
+ this.app.use('/api/costs', costsRouter);
256
+
257
+ // SPA fallback — serve index.html for any non-API GET
258
+ this.app.get('*', (req, res) => {
259
+ res.sendFile(path.join(this.clientDistPath, 'index.html'));
260
+ });
261
+
262
+ // 404 handler
263
+ this.app.use((req, res) => {
264
+ res.status(404).json({ error: 'Not found' });
265
+ });
266
+
267
+ // Error handler
268
+ this.app.use((err, req, res, next) => {
269
+ console.error('Server error:', err);
270
+ res.status(500).json({ error: 'Internal server error' });
271
+ });
272
+ }
273
+
274
+ /**
275
+ * Load work items from file system
276
+ */
277
+ async loadWorkItems() {
278
+ console.log('Loading work items...');
279
+
280
+ try {
281
+ // Scan for work.json files
282
+ const workFiles = await this.scanner.scan();
283
+ console.log(`Found ${workFiles.length} work items`);
284
+
285
+ if (workFiles.length === 0) {
286
+ console.warn('No work items found in .avc/project/');
287
+ this.hierarchy = { items: new Map(), roots: [] };
288
+ return;
289
+ }
290
+
291
+ // Read all work items
292
+ const workItems = await this.reader.readAllWorkItems(workFiles);
293
+ console.log(`Successfully read ${workItems.length} work items`);
294
+
295
+ // Build hierarchy
296
+ this.hierarchy = this.hierarchyBuilder.buildHierarchy(workItems);
297
+ console.log(`Built hierarchy with ${this.hierarchy.roots.length} root epics`);
298
+ } catch (error) {
299
+ console.error('Error loading work items:', error);
300
+ throw error;
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Setup file watcher for real-time updates
306
+ */
307
+ setupFileWatcher() {
308
+ console.log('Setting up file watcher...');
309
+
310
+ this.fileWatcher.on('ready', () => {
311
+ console.log('File watcher ready');
312
+ });
313
+
314
+ this.fileWatcher.on('added', async (filePath) => {
315
+ console.log(`Work item added: ${filePath}`);
316
+ await this.reloadWorkItems();
317
+ if (this.websocket) {
318
+ this.websocket.broadcastRefresh();
319
+ }
320
+ });
321
+
322
+ this.fileWatcher.on('changed', async (filePath) => {
323
+ console.log(`Work item changed: ${filePath}`);
324
+ await this.reloadWorkItems();
325
+ if (this.websocket) {
326
+ this.websocket.broadcastRefresh();
327
+ }
328
+ });
329
+
330
+ this.fileWatcher.on('deleted', async (filePath) => {
331
+ console.log(`Work item deleted: ${filePath}`);
332
+ await this.reloadWorkItems();
333
+ if (this.websocket) {
334
+ this.websocket.broadcastRefresh();
335
+ }
336
+ });
337
+
338
+ this.fileWatcher.on('error', (error) => {
339
+ console.error('File watcher error:', error);
340
+ });
341
+
342
+ this.fileWatcher.start();
343
+
344
+ // Watch doc.md for changes → sync to .avc/documentation/index.md
345
+ // so vitepress dev picks up the change and hot-reloads the browser
346
+ const docMdPath = path.join(this.projectRoot, '.avc', 'project', 'doc.md');
347
+ const docsIndexPath = path.join(this.projectRoot, '.avc', 'documentation', 'index.md');
348
+
349
+ const docWatcher = chokidar.watch(docMdPath, {
350
+ persistent: true,
351
+ ignoreInitial: true,
352
+ usePolling: true,
353
+ interval: 2000,
354
+ awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 },
355
+ });
356
+
357
+ docWatcher.on('change', async () => {
358
+ try {
359
+ const docsDir = path.join(this.projectRoot, '.avc', 'documentation');
360
+ const docsDirExists = await fs.access(docsDir).then(() => true).catch(() => false);
361
+ if (!docsDirExists) return; // documentation not set up yet
362
+ const content = await fs.readFile(docMdPath, 'utf8');
363
+ await fs.writeFile(docsIndexPath, content, 'utf8');
364
+ console.log('[doc-watcher] Synced doc.md → documentation/index.md');
365
+ } catch (err) {
366
+ console.error('[doc-watcher] Sync failed:', err.message);
367
+ }
368
+ });
369
+
370
+ docWatcher.on('error', (err) => {
371
+ console.error('[doc-watcher] Error:', err.message);
372
+ });
373
+
374
+ // Watch token-history.json → broadcast cost:update so the dashboard
375
+ // chip refreshes immediately after any API call writes token usage.
376
+ const tokenHistoryPath = path.join(this.projectRoot, '.avc', 'token-history.json');
377
+ const tokenWatcher = chokidar.watch(tokenHistoryPath, {
378
+ persistent: true,
379
+ ignoreInitial: true,
380
+ usePolling: true,
381
+ interval: 1000,
382
+ awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 },
383
+ });
384
+
385
+ tokenWatcher.on('change', () => {
386
+ if (this.websocket) {
387
+ this.websocket.broadcastCostUpdate();
388
+ }
389
+ });
390
+
391
+ tokenWatcher.on('error', (err) => {
392
+ console.error('[token-watcher] Error:', err.message);
393
+ });
394
+ }
395
+
396
+ /**
397
+ * Reload work items from file system
398
+ */
399
+ async reloadWorkItems() {
400
+ try {
401
+ await this.loadWorkItems();
402
+ } catch (error) {
403
+ console.error('Error reloading work items:', error);
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Get current hierarchy
409
+ * @returns {object} Current hierarchy
410
+ */
411
+ getHierarchy() {
412
+ return this.hierarchy || { items: new Map(), roots: [] };
413
+ }
414
+
415
+ /**
416
+ * Get full details for a work item
417
+ * @param {object} item - Work item
418
+ * @returns {Promise<object>} Full details
419
+ */
420
+ async getFullDetails(item) {
421
+ return await this.reader.getFullDetails(item);
422
+ }
423
+
424
+ /**
425
+ * Start the server
426
+ */
427
+ async start() {
428
+ try {
429
+ // Load initial data
430
+ await this.loadWorkItems();
431
+
432
+ // Create HTTP server
433
+ this.server = http.createServer(this.app);
434
+
435
+ // Allow long-running LLM requests (local models can be very slow)
436
+ this.server.timeout = 30 * 60 * 1000; // 30 min socket timeout
437
+ this.server.headersTimeout = 30 * 60 * 1000; // 30 min headers timeout
438
+ this.server.requestTimeout = 30 * 60 * 1000; // 30 min request timeout
439
+
440
+ // Setup WebSocket (pass processRegistry for init message to new clients)
441
+ this.websocket = setupWebSocket(this.server, this, this.processRegistry, this.ceremonyService);
442
+
443
+ // Wire ceremony service to WebSocket for broadcasting
444
+ this.ceremonyService.setWebSocket(this.websocket);
445
+ this.ceremonyService.setReloadCallback(() => this.reloadWorkItems());
446
+
447
+ // Wire task runner service to WebSocket for broadcasting
448
+ this.taskRunnerService.setWebSocket(this.websocket);
449
+ this.taskRunnerService.setReloadCallback(() => this.reloadWorkItems());
450
+
451
+ // Wire refine service to WebSocket
452
+ this.refineService.websocket = this.websocket;
453
+
454
+ // Wire ProcessRegistry events → WebSocket broadcasts
455
+ this.processRegistry.on('created', (record) => {
456
+ this.websocket?.broadcastProcessStarted(record);
457
+ });
458
+ this.processRegistry.on('status', (processId, status, record) => {
459
+ this.websocket?.broadcastProcessStatus(processId, status, {
460
+ result: record.result,
461
+ error: record.error,
462
+ endedAt: record.endedAt,
463
+ });
464
+ });
465
+
466
+ // Setup file watcher
467
+ this.setupFileWatcher();
468
+
469
+ // Start listening
470
+ await new Promise((resolve, reject) => {
471
+ this.server.listen(this.port, this.host, (error) => {
472
+ if (error) {
473
+ reject(error);
474
+ } else {
475
+ console.log(`\nKanban server listening on http://${this.host}:${this.port}`);
476
+ console.log(`WebSocket available at ws://${this.host}:${this.port}/ws`);
477
+ console.log(`API endpoints:`);
478
+ console.log(` GET /api/health`);
479
+ console.log(` GET /api/stats`);
480
+ console.log(` GET /api/work-items`);
481
+ console.log(` GET /api/work-items/grouped`);
482
+ console.log(` GET /api/work-items/:id`);
483
+ console.log(` GET /api/work-items/:id/doc\n`);
484
+ resolve();
485
+ }
486
+ });
487
+ });
488
+ } catch (error) {
489
+ console.error('Error starting server:', error);
490
+ throw error;
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Stop the server
496
+ */
497
+ async stop() {
498
+ console.log('Stopping kanban server...');
499
+
500
+ // Stop file watcher
501
+ if (this.fileWatcher) {
502
+ await this.fileWatcher.stop();
503
+ }
504
+
505
+ // Close WebSocket connections
506
+ if (this.websocket && this.websocket.wss) {
507
+ this.websocket.wss.clients.forEach((client) => {
508
+ client.close();
509
+ });
510
+ this.websocket.wss.close();
511
+ }
512
+
513
+ // Close HTTP server
514
+ if (this.server) {
515
+ await new Promise((resolve) => {
516
+ this.server.close(() => {
517
+ console.log('Kanban server stopped');
518
+ resolve();
519
+ });
520
+ });
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Get server status
526
+ * @returns {object} Server status
527
+ */
528
+ getStatus() {
529
+ return {
530
+ running: this.server !== null,
531
+ port: this.port,
532
+ host: this.host,
533
+ workItemCount: this.hierarchy ? this.hierarchy.items.size : 0,
534
+ websocketClients: this.websocket ? this.websocket.getClientCount() : 0,
535
+ };
536
+ }
537
+ }