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