UNPKG

@pimzino/claude-code-spec-workflow

Version:

Automated workflows for Claude Code. Includes spec-driven development (Requirements → Design → Tasks → Implementation) with intelligent orchestration, optional steering documents and streamlined bug fix workflow (Report → Analyze → Fix → Verify). We have

217 lines 8.69 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DashboardServer = void 0; const fastify_1 = __importDefault(require("fastify")); const static_1 = __importDefault(require("@fastify/static")); const websocket_1 = __importDefault(require("@fastify/websocket")); const path_1 = require("path"); const promises_1 = require("fs/promises"); const watcher_1 = require("./watcher"); const parser_1 = require("./parser"); const open_1 = __importDefault(require("open")); const utils_1 = require("../utils"); const git_1 = require("../git"); const logger_1 = require("./logger"); class DashboardServer { constructor(options) { this.clients = new Set(); this.options = options; this.parser = new parser_1.SpecParser(options.projectPath); this.watcher = new watcher_1.SpecWatcher(options.projectPath, this.parser); this.app = (0, fastify_1.default)({ logger: false }); } async start() { // Register plugins await this.app.register(static_1.default, { root: (0, path_1.join)(__dirname, 'public'), prefix: '/', }); await this.app.register(websocket_1.default); // WebSocket endpoint for real-time updates const self = this; this.app.register(async function (fastify) { fastify.get('/ws', { websocket: true }, (connection) => { const socket = connection.socket; (0, logger_1.debug)('WebSocket client connected'); // Add client to set self.clients.add(socket); // Send initial state Promise.all([ self.parser.getAllSpecs(), self.parser.getAllBugs() ]) .then(([specs, bugs]) => { socket.send(JSON.stringify({ type: 'initial', data: { specs, bugs }, })); }) .catch((error) => { console.error('Error getting initial data:', error); }); // Handle client disconnect socket.on('close', () => { self.clients.delete(socket); }); socket.on('error', (error) => { console.error('WebSocket error:', error); self.clients.delete(socket); }); }); }); // Serve Claude icon as favicon this.app.get('/favicon.ico', async (request, reply) => { return reply.sendFile('claude-icon.svg'); }); // API endpoints this.app.get('/api/test', async () => { return { message: 'Test endpoint works!' }; }); this.app.get('/api/specs', async () => { const specs = await this.parser.getAllSpecs(); return specs; }); this.app.get('/api/bugs', async () => { const bugs = await this.parser.getAllBugs(); return bugs; }); this.app.get('/api/info', async () => { const projectName = this.options.projectPath.split('/').pop() || 'Project'; const gitInfo = await git_1.GitUtils.getGitInfo(this.options.projectPath); const steeringStatus = await this.parser.getProjectSteeringStatus(); return { projectName, steering: steeringStatus, ...gitInfo }; }); this.app.get('/api/specs/:name', async (request, reply) => { const { name } = request.params; const spec = await this.parser.getSpec(name); if (!spec) { reply.code(404).send({ error: 'Spec not found' }); } return spec; }); this.app.get('/api/bugs/:name', async (request, reply) => { const { name } = request.params; const bug = await this.parser.getBug(name); if (!bug) { reply.code(404).send({ error: 'Bug not found' }); } return bug; }); // Get raw markdown content this.app.get('/api/specs/:name/:document', async (request, reply) => { const { name, document } = request.params; const allowedDocs = ['requirements', 'design', 'tasks']; if (!allowedDocs.includes(document)) { reply.code(400).send({ error: 'Invalid document type' }); return; } const docPath = (0, path_1.join)(this.options.projectPath, '.claude', 'specs', name, `${document}.md`); try { const content = await (0, promises_1.readFile)(docPath, 'utf-8'); return { content }; } catch { reply.code(404).send({ error: 'Document not found' }); } }); // Get raw bug markdown content this.app.get('/api/bugs/:name/:document', async (request, reply) => { const { name, document } = request.params; const allowedDocs = ['report', 'analysis', 'verification']; if (!allowedDocs.includes(document)) { reply.code(400).send({ error: 'Invalid document type' }); return; } const docPath = (0, path_1.join)(this.options.projectPath, '.claude', 'bugs', name, `${document}.md`); try { const content = await (0, promises_1.readFile)(docPath, 'utf-8'); return { content }; } catch { reply.code(404).send({ error: 'Document not found' }); } }); // Set up file watcher this.watcher.on('change', (event) => { // Broadcast to all connected clients const message = JSON.stringify({ type: 'update', data: event, }); this.clients.forEach((client) => { if (client.readyState === 1) { // WebSocket.OPEN client.send(message); } }); }); // Set up bug change watcher this.watcher.on('bug-change', (event) => { // Broadcast to all connected clients const message = JSON.stringify({ type: 'bug-update', data: event, }); this.clients.forEach((client) => { if (client.readyState === 1) { // WebSocket.OPEN client.send(message); } }); }); // Set up steering change watcher this.watcher.on('steering-change', (event) => { // Broadcast steering updates to all connected clients const message = JSON.stringify({ type: 'steering-update', data: event.steeringStatus, }); this.clients.forEach((client) => { if (client.readyState === 1) { // WebSocket.OPEN client.send(message); } }); }); // Start watcher await this.watcher.start(); // Find available port if the requested port is busy let actualPort = this.options.port; if (!(await (0, utils_1.isPortAvailable)(this.options.port))) { console.log(`Port ${this.options.port} is in use, finding alternative...`); actualPort = await (0, utils_1.findAvailablePort)(this.options.port); console.log(`Using port ${actualPort} instead`); } // Start server await this.app.listen({ port: actualPort, host: '0.0.0.0' }); // Update the port in options for URL generation this.options.port = actualPort; // Open browser if requested if (this.options.autoOpen) { await (0, open_1.default)(`http://localhost:${this.options.port}`); } } async stop() { // Close all WebSocket connections this.clients.forEach((client) => { if (client.readyState === 1) { // WebSocket.OPEN client.close(); } }); this.clients.clear(); // Stop the watcher await this.watcher.stop(); // Close the Fastify server await this.app.close(); } } exports.DashboardServer = DashboardServer; //# sourceMappingURL=server.js.map