Skip to main content
Glama

Spec Workflow MCP

FileWatcher.ts3.67 kB
import * as vscode from 'vscode'; import * as path from 'path'; export interface FileChangeEvent { type: 'created' | 'changed' | 'deleted'; uri: vscode.Uri; relativePath: string; } export class FileWatcher { private _disposables: vscode.Disposable[] = []; private _onFileChanged = new vscode.EventEmitter<FileChangeEvent>(); private _watcher: vscode.FileSystemWatcher | null = null; public readonly onFileChanged = this._onFileChanged.event; constructor() { // Listen for workspace folder changes to update watcher this._disposables.push( vscode.workspace.onDidChangeWorkspaceFolders(() => { this.updateWatcher(); }) ); this.updateWatcher(); } private updateWatcher() { // Dispose existing watcher if (this._watcher) { this._watcher.dispose(); this._watcher = null; } const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { return; } const workspaceRoot = workspaceFolders[0].uri.fsPath; const specWorkflowPattern = new vscode.RelativePattern( workspaceRoot, '.spec-workflow/**/*' ); // Create new watcher for .spec-workflow directory this._watcher = vscode.workspace.createFileSystemWatcher(specWorkflowPattern); // Listen for file events this._watcher.onDidCreate(uri => { this.emitFileChange('created', uri); }); this._watcher.onDidChange(uri => { this.emitFileChange('changed', uri); }); this._watcher.onDidDelete(uri => { this.emitFileChange('deleted', uri); }); this._disposables.push(this._watcher); } private emitFileChange(type: 'created' | 'changed' | 'deleted', uri: vscode.Uri) { const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { return; } const workspaceRoot = workspaceFolders[0].uri.fsPath; const relativePath = path.relative(workspaceRoot, uri.fsPath); // Only emit for relevant files if (this.isRelevantFile(relativePath)) { this._onFileChanged.fire({ type, uri, relativePath }); } } private isRelevantFile(relativePath: string): boolean { // Only watch for changes in spec files, steering files, and approvals const normalizedPath = relativePath.replace(/\\/g, '/'); return ( // Spec documents normalizedPath.includes('.spec-workflow/specs/') && (normalizedPath.endsWith('.md') || normalizedPath.endsWith('.json')) || // Steering documents normalizedPath.includes('.spec-workflow/steering/') && normalizedPath.endsWith('.md') || // Approval files normalizedPath.includes('.spec-workflow/approvals/') && normalizedPath.endsWith('.json') ); } public getWatchedFileType(relativePath: string): 'spec' | 'steering' | 'approval' | null { const normalizedPath = relativePath.replace(/\\/g, '/'); if (normalizedPath.includes('.spec-workflow/specs/')) { return 'spec'; } else if (normalizedPath.includes('.spec-workflow/steering/')) { return 'steering'; } else if (normalizedPath.includes('.spec-workflow/approvals/')) { return 'approval'; } return null; } public extractSpecName(relativePath: string): string | null { const normalizedPath = relativePath.replace(/\\/g, '/'); const match = normalizedPath.match(/\.spec-workflow\/specs\/([^\/]+)/); return match ? match[1] : null; } dispose() { this._disposables.forEach(d => d.dispose()); this._onFileChanged.dispose(); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Pimzino/spec-workflow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server