This is page 1 of 12. Use http://codebase.md/pimzino/spec-workflow-mcp?page={x} to view the full context.
# Directory Structure
```
├── .dockerignore
├── .gitattributes
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ ├── dashboard_issue.yml
│ │ ├── documentation.yml
│ │ ├── feature_request.yml
│ │ └── vscode_extension.yml
│ └── workflows
│ ├── claude-code-review.yml
│ └── claude.yml
├── .gitignore
├── CHANGELOG.md
├── containers
│ ├── .env.example
│ ├── DOCKER_USAGE.md
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── example.mcp.json
│ └── README.md
├── docs
│ ├── CONFIGURATION.md
│ ├── DEVELOPMENT.md
│ ├── INTERFACES.md
│ ├── PROMPTING-GUIDE.md
│ ├── technical-documentation
│ │ ├── api-reference.md
│ │ ├── architecture.md
│ │ ├── context-management.md
│ │ ├── contributing.md
│ │ ├── dashboard.md
│ │ ├── developer-guide.md
│ │ ├── file-structure.md
│ │ ├── i18n-guide.md
│ │ ├── i18n-structure.md
│ │ ├── README.md
│ │ └── troubleshooting.md
│ ├── TOOLS-REFERENCE.md
│ ├── TROUBLESHOOTING.md
│ ├── USER-GUIDE.md
│ └── WORKFLOW.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│ ├── copy-static.cjs
│ └── validate-i18n.js
├── src
│ ├── config.ts
│ ├── core
│ │ ├── archive-service.ts
│ │ ├── dashboard-session.ts
│ │ ├── implementation-log-migrator.ts
│ │ ├── parser.ts
│ │ ├── path-utils.ts
│ │ ├── project-registry.ts
│ │ ├── task-parser.ts
│ │ └── workspace-initializer.ts
│ ├── dashboard
│ │ ├── approval-storage.ts
│ │ ├── execution-history-manager.ts
│ │ ├── implementation-log-manager.ts
│ │ ├── job-scheduler.ts
│ │ ├── multi-server.ts
│ │ ├── parser.ts
│ │ ├── project-manager.ts
│ │ ├── public
│ │ │ ├── claude-icon-dark.svg
│ │ │ └── claude-icon.svg
│ │ ├── settings-manager.ts
│ │ ├── utils.ts
│ │ └── watcher.ts
│ ├── dashboard_frontend
│ │ ├── index.html
│ │ ├── src
│ │ │ ├── components
│ │ │ │ ├── I18nErrorBoundary.tsx
│ │ │ │ └── LanguageSelector.tsx
│ │ │ ├── i18n-dynamic.ts
│ │ │ ├── i18n.ts
│ │ │ ├── lib
│ │ │ │ └── utils.ts
│ │ │ ├── locales
│ │ │ │ ├── ar.json
│ │ │ │ ├── de.json
│ │ │ │ ├── en.json
│ │ │ │ ├── es.json
│ │ │ │ ├── fr.json
│ │ │ │ ├── it.json
│ │ │ │ ├── ja.json
│ │ │ │ ├── ko.json
│ │ │ │ ├── pt.json
│ │ │ │ ├── ru.json
│ │ │ │ └── zh.json
│ │ │ ├── main.tsx
│ │ │ ├── modules
│ │ │ │ ├── api
│ │ │ │ │ └── api.tsx
│ │ │ │ ├── app
│ │ │ │ │ └── App.tsx
│ │ │ │ ├── approvals
│ │ │ │ │ ├── ApprovalsAnnotator.tsx
│ │ │ │ │ └── colors.ts
│ │ │ │ ├── components
│ │ │ │ │ ├── KanbanBoard.tsx
│ │ │ │ │ ├── KanbanTaskCard.tsx
│ │ │ │ │ ├── PageNavigationSidebar.tsx
│ │ │ │ │ ├── ProjectDropdown.tsx
│ │ │ │ │ ├── SortDropdown.tsx
│ │ │ │ │ └── StatusFilterPills.tsx
│ │ │ │ ├── diff
│ │ │ │ │ ├── DiffStats.tsx
│ │ │ │ │ ├── DiffViewer.tsx
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── editor
│ │ │ │ │ └── MarkdownEditor.tsx
│ │ │ │ ├── markdown
│ │ │ │ │ └── Markdown.tsx
│ │ │ │ ├── modals
│ │ │ │ │ ├── AlertModal.tsx
│ │ │ │ │ ├── ChangelogModal.tsx
│ │ │ │ │ ├── ConfirmationModal.tsx
│ │ │ │ │ └── TextInputModal.tsx
│ │ │ │ ├── notifications
│ │ │ │ │ ├── NotificationProvider.tsx
│ │ │ │ │ ├── VolumeControl.module.css
│ │ │ │ │ └── VolumeControl.tsx
│ │ │ │ ├── pages
│ │ │ │ │ ├── ApprovalsPage.tsx
│ │ │ │ │ ├── DashboardStatistics.tsx
│ │ │ │ │ ├── JobExecutionHistory.tsx
│ │ │ │ │ ├── JobFormModal.tsx
│ │ │ │ │ ├── JobTemplates.ts
│ │ │ │ │ ├── LogsPage.tsx
│ │ │ │ │ ├── SettingsPage.tsx
│ │ │ │ │ ├── SpecsPage.tsx
│ │ │ │ │ ├── SpecViewerPage.tsx
│ │ │ │ │ ├── SteeringPage.tsx
│ │ │ │ │ └── TasksPage.tsx
│ │ │ │ ├── projects
│ │ │ │ │ └── ProjectProvider.tsx
│ │ │ │ ├── theme
│ │ │ │ │ ├── HighlightStyles.tsx
│ │ │ │ │ ├── tailwind.css
│ │ │ │ │ ├── theme.css
│ │ │ │ │ └── ThemeProvider.tsx
│ │ │ │ └── ws
│ │ │ │ └── WebSocketProvider.tsx
│ │ │ └── types.ts
│ │ └── vite.config.ts
│ ├── index.ts
│ ├── markdown
│ │ └── templates
│ │ ├── design-template.md
│ │ ├── product-template.md
│ │ ├── requirements-template.md
│ │ ├── structure-template.md
│ │ ├── tasks-template.md
│ │ └── tech-template.md
│ ├── prompts
│ │ ├── create-spec.ts
│ │ ├── create-steering-doc.ts
│ │ ├── implement-task.ts
│ │ ├── index.ts
│ │ ├── inject-spec-workflow-guide.ts
│ │ ├── inject-steering-guide.ts
│ │ ├── refresh-tasks.ts
│ │ ├── spec-status.ts
│ │ └── types.ts
│ ├── server.ts
│ ├── tools
│ │ ├── __tests__
│ │ │ └── projectPath.test.ts
│ │ ├── approvals.ts
│ │ ├── index.ts
│ │ ├── log-implementation.ts
│ │ ├── spec-status.ts
│ │ ├── spec-workflow-guide.ts
│ │ └── steering-guide.ts
│ └── types.ts
├── tsconfig.json
├── vitest.config.ts
└── vscode-extension
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── .vscode-test.mjs
├── .vscodeignore
├── CHANGELOG.md
├── components.json
├── esbuild.js
├── eslint.config.mjs
├── icon.png
├── icons
│ ├── activity-bar-icon.svg
│ └── spec-workflow.svg
├── LICENSE
├── package-lock.json
├── package.json
├── package.nls.ja.json
├── package.nls.json
├── package.nls.zh.json
├── README.md
├── src
│ ├── extension
│ │ ├── providers
│ │ │ └── SidebarProvider.ts
│ │ ├── services
│ │ │ ├── ApprovalCommandService.ts
│ │ │ ├── ApprovalEditorService.ts
│ │ │ ├── ArchiveService.ts
│ │ │ ├── CommentModalService.ts
│ │ │ ├── FileWatcher.ts
│ │ │ ├── ImplementationLogService.ts
│ │ │ └── SpecWorkflowService.ts
│ │ ├── types.ts
│ │ └── utils
│ │ ├── colorUtils.ts
│ │ ├── logger.ts
│ │ └── taskParser.ts
│ ├── extension.ts
│ ├── test
│ │ ├── extension.test.ts
│ │ └── pathResolution.test.ts
│ └── webview
│ ├── App.tsx
│ ├── comment-modal.html
│ ├── comment-modal.tsx
│ ├── components
│ │ ├── ArtifactSection.tsx
│ │ ├── CommentModal.tsx
│ │ ├── LogEntryCard.tsx
│ │ ├── LogStatsPanel.tsx
│ │ └── ui
│ │ ├── accordion.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── input.tsx
│ │ ├── progress.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ └── tabs.tsx
│ ├── globals.css
│ ├── hooks
│ │ ├── useSoundNotifications.ts
│ │ └── useVSCodeTheme.ts
│ ├── i18n.ts
│ ├── index.html
│ ├── lib
│ │ ├── utils.ts
│ │ └── vscode-api.ts
│ ├── locales
│ │ ├── ar.json
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── it.json
│ │ ├── ja.json
│ │ ├── ko.json
│ │ ├── pt.json
│ │ ├── ru.json
│ │ └── zh.json
│ ├── main.tsx
│ ├── pages
│ │ └── LogsPage.tsx
│ └── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.app.json
├── tsconfig.extension.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
├── webview-assets
│ └── sounds
│ ├── approval-pending.wav
│ └── task-completed.wav
└── webview-dist
├── comment-modal.html
├── comment-modal.js
├── globals.css
├── i18n.js
├── index.html
├── locales
│ ├── ar.json
│ ├── de.json
│ ├── en.json
│ ├── es.json
│ ├── fr.json
│ ├── it.json
│ ├── ja.json
│ ├── ko.json
│ ├── pt.json
│ ├── ru.json
│ └── zh.json
├── main.js
└── sounds
├── approval-pending.wav
└── task-completed.wav
```
# Files
--------------------------------------------------------------------------------
/vscode-extension/.gitignore:
--------------------------------------------------------------------------------
```
out
dist
node_modules
.vscode-test/
*.vsix
```
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
```
# Auto detect text files and perform LF normalization
* text=auto
```
--------------------------------------------------------------------------------
/vscode-extension/.vscode-test.mjs:
--------------------------------------------------------------------------------
```
import { defineConfig } from '@vscode/test-cli';
export default defineConfig({
files: 'out/test/**/*.test.js',
});
```
--------------------------------------------------------------------------------
/vscode-extension/.vscodeignore:
--------------------------------------------------------------------------------
```
.vscode/**
.vscode-test/**
out/**
node_modules/**
src/**
.gitignore
.yarnrc
esbuild.js
vsc-extension-quickstart.md
**/tsconfig.json
**/eslint.config.mjs
**/*.map
**/*.ts
**/.vscode-test.*
!webview-dist/**
!package.nls.*.json
```
--------------------------------------------------------------------------------
/containers/.env.example:
--------------------------------------------------------------------------------
```
# Dashboard Configuration
# Default port for the dashboard (change if 5000 is in use)
DASHBOARD_PORT=5000
# Path to your project directory
# This should be the directory containing (or where you want to create) .spec-workflow
SPEC_WORKFLOW_PATH=./workspace
```
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules
npm-debug.log
# Build artifacts
dist
.next
out
# IDE and editor files
.vscode
.idea
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
# Git
.git
.gitignore
.gitattributes
# Testing
coverage
.nyc_output
# Documentation (not needed in container)
docs
*.md
!containers/README.md
# CI/CD
.github
# Environment files
.env
.env.*
# VSCode extension
vscode-extension
# Temporary files
tmp
temp
*.tmp
.spec-workflow
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# AI
CLAUDE.md
.claude
.serena
AGENTS.md
opencodetmp
# Spec Workflow MCP
.specworkflow
```
--------------------------------------------------------------------------------
/vscode-extension/README.md:
--------------------------------------------------------------------------------
```markdown
# Spec Workflow MCP Extension
A VSCode extension that provides an integrated dashboard for managing Spec-Workflow MCP projects directly in your workspace.
## Features
- **Integrated Sidebar Dashboard**: Access all your spec workflow data without leaving VSCode
- **Real-time Updates**: File system watchers automatically update the dashboard when .spec-workflow files change
- **Project Overview**: Comprehensive statistics showing active vs archived specs, tasks, and approvals
- **Spec Management**: Browse active and archived specifications with easy archiving/unarchiving
- **Task Management**: View and update task statuses directly from the sidebar
- **Approval Workflow**: Complete approval process with approve, reject, and revision requests
- **Steering Documents**: Manage product, tech, and structure steering documents
- **Sound Notifications**: Configurable audio alerts for approvals and task completions
- **Editor Integration**: Context menu actions for approvals and comments directly in code
- **React + Tailwind UI**: Modern, responsive interface built with React 19 and Tailwind CSS v4
## Requirements
- VSCode 1.99.0 or higher
- A workspace containing a `.spec-workflow` directory structure
## Usage
1. Open a workspace that contains a `.spec-workflow` directory
2. The Spec Workflow MCP icon will appear in the Activity Bar
3. Click the icon to open the dashboard sidebar
4. Use the tabbed interface to navigate between:
- **Overview**: Project statistics showing active/archived specs breakdown and recent activity
- **Steering**: Manage steering documents (product, tech, structure)
- **Specs**: Browse active and archived specifications with archive management
- **Tasks**: View and manage task progress for selected specifications
- **Approvals**: Handle pending approval requests with full workflow support
## Archive Management
Specifications can be archived to keep dropdown menus clean and organized:
- Switch between **Active** and **Archived** views in the Specs tab
- Archive completed specifications to remove them from active dropdowns
- Unarchive specifications when needed
- Archive operations are blocked if pending approvals exist for the specification
## Commands
- `Spec Workflow: Open Dashboard` - Opens the sidebar dashboard
- `Spec Workflow: Refresh Data` - Manually refresh all data
- `Spec Workflow: Open Spec` - Quick pick to open specific specifications
- `Spec Workflow: Approve` - Approve current document (editor context)
- `Spec Workflow: Reject` - Reject current document (editor context)
- `Spec Workflow: Request Revision` - Request revision for current document
- `Spec Workflow: Add Comment` - Add comment to selected text
- `Spec Workflow: Approval Actions` - Show approval action menu
## Extension Settings
- `specWorkflow.notifications.sounds.enabled` - Enable sound notifications (default: true)
- `specWorkflow.notifications.sounds.volume` - Sound volume level 0.0-1.0 (default: 0.3)
- `specWorkflow.notifications.sounds.approvalSound` - Play sound for approval requests (default: true)
- `specWorkflow.notifications.sounds.taskCompletionSound` - Play sound for task completions (default: true)
## Development
This extension is built with:
- React 19 with TypeScript
- Vite for webview bundling
- Tailwind CSS v4 for styling
- ShadCN UI components
- VSCode Extension API
## Release Notes
### 0.0.1
Initial release of Spec Workflow MCP Extension:
- **Dashboard Integration**: Complete sidebar dashboard with real-time updates
- **Specification Management**: Active/archived spec organization with archive workflow
- **Task Tracking**: Interactive task management with status updates
- **Approval System**: Full approval workflow with approve/reject/revision capabilities
- **Steering Documents**: Product, tech, and structure document management
- **Sound Notifications**: Configurable audio alerts for key events
- **Editor Integration**: Context menu actions and comment system
- **Modern UI**: React 19 + Tailwind CSS v4 with responsive design
## Support
If you find this extension helpful, consider supporting the development:
[☕ Buy Me a Coffee](https://buymeacoffee.com/pimzino)
## License
This project is licensed under the GPL-3.0 License.
```
--------------------------------------------------------------------------------
/containers/README.md:
--------------------------------------------------------------------------------
```markdown
# Spec-Workflow MCP Docker Setup
This directory contains Docker configuration files to run the Spec-Workflow MCP dashboard in a containerized environment. This setup provides isolation and easy deployment for the dashboard service.
## 📋 Table of Contents
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [Building the Image](#building-the-image)
- [Running the Dashboard](#running-the-dashboard)
- [Using Docker Compose](#using-docker-compose)
- [Configuration Options](#configuration-options)
- [Troubleshooting](#troubleshooting)
## Prerequisites
- Docker (version 20.10 or later)
- Docker Compose (optional, for simplified management)
- A project directory where you want to use spec-workflow
## Quick Start
### Option 1: Using Docker Compose (Recommended)
The easiest way to get started is with Docker Compose:
```bash
# From the repository root
cd containers
docker-compose up --build
```
The dashboard will be available at: http://localhost:5000
### Option 2: Using Docker CLI
Build and run manually:
```bash
# From the repository root
docker build -f containers/Dockerfile -t spec-workflow-mcp .
docker run -p 5000:5000 -v "./workspace/.spec-workflow:/workspace/.spec-workflow:rw" spec-workflow-mcp
```
## Building the Image
### Build from Repository Root
**Important:** The Dockerfile must be built from the repository root directory, not from the `containers` directory, because it needs access to the source code.
```bash
# From the repository root
docker build -f containers/Dockerfile -t spec-workflow-mcp .
```
### Build Arguments
The image is built in two stages:
1. **Builder stage**: Installs dependencies and builds the TypeScript application
2. **Runtime stage**: Creates a minimal production image with only necessary files
## Running the Dashboard
### Basic Usage
Run the dashboard on the default port (5000):
```bash
docker run -p 5000:5000 \
-v "./workspace/.spec-workflow:/workspace/.spec-workflow:rw" \
spec-workflow-mcp
```
### Custom Port
Run the dashboard on a custom port (e.g., 8080):
```bash
docker run -p 8080:8080 \
-e DASHBOARD_PORT=8080 \
-v "./workspace/.spec-workflow:/workspace/.spec-workflow:rw" \
spec-workflow-mcp
```
### Using a Specific Project Path
Mount your project's `.spec-workflow` directory:
```bash
docker run -p 5000:5000 \
-v "/path/to/your/project/.spec-workflow:/workspace/.spec-workflow:rw" \
spec-workflow-mcp
```
## Using Docker Compose
Docker Compose simplifies the management of the dashboard container.
### Default Configuration
Create a `.env` file (optional):
```bash
# .env file
DASHBOARD_PORT=5000
SPEC_WORKFLOW_PATH=./workspace
```
Then start the dashboard:
```bash
cd containers
docker-compose up -d
```
### Custom Configuration
Override environment variables when starting:
```bash
DASHBOARD_PORT=8080 SPEC_WORKFLOW_PATH=/path/to/project docker-compose up -d
```
### Managing the Service
```bash
# Start the dashboard
docker-compose up -d
# View logs
docker-compose logs -f
# Stop the dashboard
docker-compose down
# Rebuild and restart
docker-compose up --build
```
## Configuration Options
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `DASHBOARD_PORT` | `5000` | Port on which the dashboard runs |
| `SPEC_WORKFLOW_PATH` | `/workspace` | Path to the project directory (inside container) |
### Volume Mounts
The dashboard requires access to the `.spec-workflow` directory to function properly.
**Example:**
```bash
-v "/path/to/project/.spec-workflow:/workspace/.spec-workflow:rw"
```
**Important Notes:**
- The volume mount must be read-write (`:rw`) for the dashboard to function
- Only the `.spec-workflow` directory needs to be mounted
- The directory will be created automatically if it doesn't exist
### Port Mapping
Map the container port to a host port:
```bash
-p <host-port>:<container-port>
```
**Examples:**
- Default: `-p 5000:5000`
- Custom: `-p 8080:8080` (remember to set `DASHBOARD_PORT=8080`)
## MCP Server Configuration
The dashboard runs independently of MCP servers. To connect MCP servers to the dashboard:
### For Claude Desktop
Add to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"spec-workflow": {
"command": "npx",
"args": ["-y", "@pimzino/spec-workflow-mcp@latest", "/path/to/your/project"]
}
}
}
```
**Note:** The MCP server runs on your host machine and connects to the Docker dashboard automatically via port 5000.
### For Other MCP Clients
Use similar configuration with the appropriate MCP client settings. The MCP servers run independently and connect to the dashboard's WebSocket endpoint.
## Troubleshooting
### Common Issues
#### 1. Port Already in Use
**Error:** `Bind for 0.0.0.0:5000 failed: port is already allocated`
**Solution:** Use a different port:
```bash
docker run -p 8080:8080 -e DASHBOARD_PORT=8080 ...
# or with docker-compose
DASHBOARD_PORT=8080 docker-compose up
```
#### 2. Permission Denied
**Error:** Permission issues with `.spec-workflow` directory
**Solutions:**
- Ensure the directory has proper permissions: `chmod -R 755 .spec-workflow`
- On SELinux systems, add `:z` to the volume mount: `-v "./workspace/.spec-workflow:/workspace/.spec-workflow:rw,z"`
#### 3. Dashboard Not Accessible
**Check:**
- Container is running: `docker ps`
- Port is properly mapped: `docker port <container-id>`
- Firewall allows connections on the port
- Access via: `http://localhost:5000` (or your custom port)
#### 4. Build Fails
**Error:** Build fails with COPY or dependency errors
**Solutions:**
- Ensure you're building from the repository root: `docker build -f containers/Dockerfile -t spec-workflow-mcp .`
- Check that all source files are present
- Verify `package.json` and `package-lock.json` exist
### Viewing Logs
#### Docker CLI
```bash
docker logs <container-id>
docker logs -f <container-id> # Follow logs
```
#### Docker Compose
```bash
docker-compose logs
docker-compose logs -f # Follow logs
```
### Inspecting the Container
```bash
# View container details
docker inspect <container-id>
# Access container shell
docker exec -it <container-id> /bin/sh
```
## Advanced Configuration
### Running in Detached Mode
```bash
docker run -d \
--name spec-workflow-dashboard \
-p 5000:5000 \
-v "./workspace/.spec-workflow:/workspace/.spec-workflow:rw" \
spec-workflow-mcp
```
### Auto-Restart on Failure
```bash
docker run -d \
--name spec-workflow-dashboard \
--restart unless-stopped \
-p 5000:5000 \
-v "./workspace/.spec-workflow:/workspace/.spec-workflow:rw" \
spec-workflow-mcp
```
### Health Checks
The dashboard doesn't currently include health checks, but you can test connectivity:
```bash
curl http://localhost:5000
```
## Security Considerations
- The container runs as a non-root user (`node`) for security
- Only expose necessary ports
- Use read-only volume mounts where possible (though `:rw` is required for `.spec-workflow`)
- Keep the base image updated: `docker pull node:24-alpine`
## Performance Tips
- The container is optimized for production with:
- Multi-stage builds to minimize image size
- Only production dependencies in final image
- Alpine Linux for small footprint
- Monitor resource usage:
```bash
docker stats <container-id>
```
## Additional Resources
- [Main Documentation](../README.md)
- [User Guide](../docs/USER-GUIDE.md)
- [Troubleshooting Guide](../docs/TROUBLESHOOTING.md)
- [GitHub Repository](https://github.com/Pimzino/spec-workflow-mcp)
## Support
If you encounter issues:
1. Check the [Troubleshooting](#troubleshooting) section
2. Review logs: `docker logs <container-id>`
3. Open an issue on [GitHub](https://github.com/Pimzino/spec-workflow-mcp/issues)
4. Include:
- Docker version: `docker --version`
- Operating system
- Error messages
- Steps to reproduce
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Spec Workflow MCP
[](https://www.npmjs.com/package/@pimzino/spec-workflow-mcp)
[](https://marketplace.visualstudio.com/items?itemName=Pimzino.spec-workflow-mcp)
A Model Context Protocol (MCP) server for structured spec-driven development with real-time dashboard and VSCode extension.
## ☕ Support This Project
<a href="https://buymeacoffee.com/Pimzino" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
## 📺 Showcase
### 🔄 Approval System in Action
<a href="https://www.youtube.com/watch?v=C-uEa3mfxd0" target="_blank">
<img src="https://img.youtube.com/vi/C-uEa3mfxd0/maxresdefault.jpg" alt="Approval System Demo" width="600">
</a>
*See how the approval system works: create documents, request approval through the dashboard, provide feedback, and track revisions.*
### 📊 Dashboard & Spec Management
<a href="https://www.youtube.com/watch?v=g9qfvjLUWf8" target="_blank">
<img src="https://img.youtube.com/vi/g9qfvjLUWf8/maxresdefault.jpg" alt="Dashboard Demo" width="600">
</a>
*Explore the real-time dashboard: view specs, track progress, navigate documents, and monitor your development workflow.*
## ✨ Key Features
- **Structured Development Workflow** - Sequential spec creation (Requirements → Design → Tasks)
- **Real-Time Web Dashboard** - Monitor specs, tasks, and progress with live updates
- **VSCode Extension** - Integrated sidebar dashboard for VSCode users
- **Approval Workflow** - Complete approval process with revisions
- **Task Progress Tracking** - Visual progress bars and detailed status
- **Implementation Logs** - Searchable logs of all task implementations with code statistics
- **Multi-Language Support** - Available in 11 languages
## 🌍 Supported Languages
🇺🇸 English • 🇯🇵 日本語 • 🇨🇳 中文 • 🇪🇸 Español • 🇧🇷 Português • 🇩🇪 Deutsch • 🇫🇷 Français • 🇷🇺 Русский • 🇮🇹 Italiano • 🇰🇷 한국어 • 🇸🇦 العربية
## 🚀 Quick Start
### Step 1: Add to your AI tool
Add to your MCP configuration (see client-specific setup below):
```json
{
"mcpServers": {
"spec-workflow": {
"command": "npx",
"args": ["-y", "@pimzino/spec-workflow-mcp@latest", "/path/to/your/project"]
}
}
}
```
### Step 2: Choose your interface
**Option A: Web Dashboard** (Required for CLI users)
Start the dashboard (runs on port 5000 by default):
```bash
npx -y @pimzino/spec-workflow-mcp@latest --dashboard
```
The dashboard will be accessible at: http://localhost:5000
> **Note:** Only one dashboard instance is needed. All your projects will connect to the same dashboard.
**Option B: VSCode Extension** (Recommended for VSCode users)
Install [Spec Workflow MCP Extension](https://marketplace.visualstudio.com/items?itemName=Pimzino.spec-workflow-mcp) from the VSCode marketplace.
## 📝 How to Use
Simply mention spec-workflow in your conversation:
- **"Create a spec for user authentication"** - Creates complete spec workflow
- **"List my specs"** - Shows all specs and their status
- **"Execute task 1.2 in spec user-auth"** - Runs a specific task
[See more examples →](docs/PROMPTING-GUIDE.md)
## 🔧 MCP Client Setup
<details>
<summary><strong>Augment Code</strong></summary>
Configure in your Augment settings:
```json
{
"mcpServers": {
"spec-workflow": {
"command": "npx",
"args": ["-y", "@pimzino/spec-workflow-mcp@latest", "/path/to/your/project"]
}
}
}
```
</details>
<details>
<summary><strong>Claude Code CLI</strong></summary>
Add to your MCP configuration:
```bash
claude mcp add spec-workflow npx @pimzino/spec-workflow-mcp@latest -- /path/to/your/project
```
**Important Notes:**
- The `-y` flag bypasses npm prompts for smoother installation
- The `--` separator ensures the path is passed to the spec-workflow script, not to npx
- Replace `/path/to/your/project` with your actual project directory path
**Alternative for Windows (if the above doesn't work):**
```bash
claude mcp add spec-workflow cmd.exe /c "npx @pimzino/spec-workflow-mcp@latest /path/to/your/project"
```
</details>
<details>
<summary><strong>Claude Desktop</strong></summary>
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"spec-workflow": {
"command": "npx",
"args": ["-y", "@pimzino/spec-workflow-mcp@latest", "/path/to/your/project"]
}
}
}
```
> **Important:** Run the dashboard separately with `--dashboard` before starting the MCP server.
</details>
<details>
<summary><strong>Cline/Claude Dev</strong></summary>
Add to your MCP server configuration:
```json
{
"mcpServers": {
"spec-workflow": {
"command": "npx",
"args": ["-y", "@pimzino/spec-workflow-mcp@latest", "/path/to/your/project"]
}
}
}
```
</details>
<details>
<summary><strong>Continue IDE Extension</strong></summary>
Add to your Continue configuration:
```json
{
"mcpServers": {
"spec-workflow": {
"command": "npx",
"args": ["-y", "@pimzino/spec-workflow-mcp@latest", "/path/to/your/project"]
}
}
}
```
</details>
<details>
<summary><strong>Cursor IDE</strong></summary>
Add to your Cursor settings (`settings.json`):
```json
{
"mcpServers": {
"spec-workflow": {
"command": "npx",
"args": ["-y", "@pimzino/spec-workflow-mcp@latest", "/path/to/your/project"]
}
}
}
```
</details>
<details>
<summary><strong>OpenCode</strong></summary>
Add to your `opencode.json` configuration file:
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"spec-workflow": {
"type": "local",
"command": ["npx", "-y", "@pimzino/spec-workflow-mcp@latest", "/path/to/your/project"],
"enabled": true
}
}
}
```
</details>
## 🐳 Docker Deployment
Run the dashboard in a Docker container for isolated deployment:
```bash
# Using Docker Compose (recommended)
cd containers
docker-compose up --build
# Or using Docker CLI
docker build -f containers/Dockerfile -t spec-workflow-mcp .
docker run -p 5000:5000 -v "./workspace/.spec-workflow:/workspace/.spec-workflow:rw" spec-workflow-mcp
```
The dashboard will be available at: http://localhost:5000
[See Docker setup guide →](containers/README.md)
## 📚 Documentation
- [Configuration Guide](docs/CONFIGURATION.md) - Command-line options, config files
- [User Guide](docs/USER-GUIDE.md) - Comprehensive usage examples
- [Workflow Process](docs/WORKFLOW.md) - Development workflow and best practices
- [Interfaces Guide](docs/INTERFACES.md) - Dashboard and VSCode extension details
- [Prompting Guide](docs/PROMPTING-GUIDE.md) - Advanced prompting examples
- [Tools Reference](docs/TOOLS-REFERENCE.md) - Complete tools documentation
- [Development](docs/DEVELOPMENT.md) - Contributing and development setup
- [Troubleshooting](docs/TROUBLESHOOTING.md) - Common issues and solutions
## 📁 Project Structure
```
your-project/
.spec-workflow/
approvals/
archive/
specs/
steering/
templates/
user-templates/
config.example.toml
```
## 🛠️ Development
```bash
# Install dependencies
npm install
# Build the project
npm run build
# Run in development mode
npm run dev
```
[See development guide →](docs/DEVELOPMENT.md)
## 📄 License
GPL-3.0
## ⭐ Star History
<a href="https://www.star-history.com/#Pimzino/spec-workflow-mcp&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Pimzino/spec-workflow-mcp&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Pimzino/spec-workflow-mcp&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Pimzino/spec-workflow-mcp&type=Date" />
</picture>
</a>
```
--------------------------------------------------------------------------------
/docs/technical-documentation/README.md:
--------------------------------------------------------------------------------
```markdown
# Technical Documentation
> **Quick Reference**: Jump to what you need most → [Tools API](api-reference.md) | [Architecture](architecture.md) | [Developer Guide](developer-guide.md) | [Troubleshooting](troubleshooting.md)
## 📋 Table of Contents
### Core Documentation
- **[Architecture Overview](architecture.md)** - System design, components, and data flow
- **[MCP Tools API Reference](api-reference.md)** - Complete tool documentation with examples
- **[Developer Workflow Guide](developer-guide.md)** - Step-by-step development workflows
- **[Context Management](context-management.md)** - How context switching and caching works
- **[File Structure](file-structure.md)** - Project organization and directory layout
- **[Dashboard System](dashboard.md)** - Web dashboard and real-time features
- **[Troubleshooting & FAQ](troubleshooting.md)** - Common issues and solutions
### Quick Start Guides
- **[Setting Up Development Environment](setup.md)** - Get up and running quickly
- **[Contributing Guidelines](contributing.md)** - How to contribute to the project
- **[Testing Guide](testing.md)** - Running tests and writing new ones
## 🚀 Quick Start
### For AI Assistant Integration
```json
{
"mcpServers": {
"spec-workflow": {
"command": "npx",
"args": ["-y", "@pimzino/spec-workflow-mcp@latest", "/path/to/project", "--AutoStartDashboard"]
}
}
}
```
### For Local Development
```bash
# Clone and setup
git clone <repository-url>
cd spec-workflow-mcp
npm install
# Start development server
npm run dev
# Build for production
npm run build
```
## 🔍 Comprehensive Capability Analysis
### Critical Questions Answered
Based on comprehensive codebase analysis, here are definitive answers to key technical questions:
#### **Question 1: Web Scraping & Research Capabilities**
**Answer: No independent web scraping - leverages LLM's built-in web search**
| Aspect | This MCP | Other AI Agents | Expansion Opportunity |
|--------|----------|----------------|---------------------|
| **Web Scraping** | ❌ No independent capability | ✅ Custom scrapers (Puppeteer, Playwright) | 🔮 Could add structured scraping tools |
| **API Research** | ❌ Relies on LLM's web search | ✅ Direct API integrations | 🔮 Could add GitHub, Stack Overflow APIs |
| **Research Caching** | ❌ No research persistence | ✅ Advanced caching systems | 🔮 Could cache LLM research results |
| **Data Sources** | ✅ LLM's vast training data + real-time web | ❌ Limited to configured sources | ✅ Best of both worlds |
#### **Question 2: AI Calls & Context Window Management**
**Answer: Pure MCP - uses only connected LLM, no independent AI calls**
| Aspect | This MCP | Other AI Agents | Expansion Opportunity |
|--------|----------|----------------|---------------------|
| **AI Service Calls** | ❌ No independent AI calls | ✅ Multiple AI model integration | 🔮 Could add specialized AI services |
| **Context Management** | ❌ No LLM context manipulation | ✅ Advanced context strategies | 🔮 Could add context optimization |
| **Memory Management** | ❌ File-based only | ✅ Vector databases, embeddings | 🔮 Could add persistent memory |
| **Multi-Model Usage** | ❌ Single LLM connection | ✅ GPT-4 + Claude + Gemini | 🔮 Could add model routing |
#### **Question 3: Document Planning Process**
**Answer: Template-guided LLM intelligence - no separate AI planning**
| Aspect | This MCP | Other AI Agents | Expansion Opportunity |
|--------|----------|----------------|---------------------|
| **Planning Intelligence** | ✅ LLM reasoning with templates | ✅ Dedicated planning AI | 🔮 Could add adaptive workflows |
| **Template System** | ✅ Static but comprehensive | ❌ Often no structured templates | ✅ Structured advantage |
| **Workflow Adaptation** | ❌ Fixed sequence | ✅ Dynamic workflow generation | 🔮 Could add LLM-powered workflows |
| **Project Analysis** | ✅ LLM analyzes project context | ✅ Specialized analysis tools | 🔮 Could add deep code analysis |
#### **Question 4: Auto Review Process**
**Answer: Human-only approval system - no automated AI review**
| Aspect | This MCP | Other AI Agents | Expansion Opportunity |
|--------|----------|----------------|---------------------|
| **Review Automation** | ❌ Human approval required | ✅ Multi-stage AI review | 🔮 Could add optional AI gates |
| **Quality Assurance** | ✅ LLM quality + Human oversight | ❌ AI-only (potential errors) | ✅ Best quality control |
| **Approval Workflows** | ✅ Dashboard/VS Code integration | ❌ Often CLI-only | ✅ Superior UX |
| **Review Intelligence** | ✅ LLM can suggest improvements | ✅ Specialized review models | 🔮 Could add review templates |
#### **Question 5: Best Practice Standards**
**Answer: LLM built-in knowledge - no external standards fetching**
| Aspect | This MCP | Other AI Agents | Expansion Opportunity |
|--------|----------|----------------|---------------------|
| **Standards Source** | ✅ LLM's vast training knowledge | ✅ External standards APIs | 🔮 Could add standards integration |
| **Currency** | ✅ LLM can web search for latest | ❌ Static configurations | ✅ Always current |
| **Customization** | ❌ No project-specific standards | ✅ Custom rule engines | 🔮 Could add org standards |
| **Best Practices** | ✅ Industry-wide via LLM | ❌ Limited to pre-configured | ✅ Comprehensive coverage |
### Competitive Positioning Analysis
**Strengths vs Other AI Agents:**
```typescript
interface CompetitiveAdvantages {
humanOversight: "Mandatory approval prevents runaway AI behavior";
llmLeverage: "Uses full power of connected LLM without limitations";
structuredOutput: "Templates ensure consistent, professional documentation";
realTimeUI: "Dashboard and VS Code integration for seamless workflow";
simplicity: "No complex setup or API key management required";
reliability: "Proven workflow sequence with validation and error handling";
}
```
**Current Limitations vs Market Leaders:**
```typescript
interface LimitationsAnalysis {
automationLevel: "Less automated than fully autonomous agents";
integrationEcosystem: "Limited external service integrations";
multiProject: "Single project scope vs enterprise-wide solutions";
aiDiversity: "Single LLM vs multi-model approaches";
workflowFlexibility: "Fixed sequence vs adaptive workflows";
}
```
**Expansion Opportunities Identified:**
```typescript
interface ExpansionRoadmap {
immediateWins: {
githubIntegration: "PR creation, issue sync, code analysis";
qualityGates: "Optional automated quality checks";
templateDynamism: "Project-type aware template selection";
};
mediumTerm: {
multiProjectSupport: "Enterprise dashboard for multiple projects";
advancedIntegrations: "Jira, Confluence, Slack notifications";
workflowCustomization: "Configurable workflow sequences";
};
longTerm: {
aiOrchestration: "Multi-agent coordination capabilities";
predictiveAnalytics: "Project success prediction and risk analysis";
enterpriseFeatures: "SSO, compliance, audit trails";
};
}
```
## ⚠️ Technical Limitations & Capabilities
### What This MCP Does NOT Do
**No Independent External Calls**:
- ❌ No separate web scraping or API calls by the MCP server
- ❌ No independent external research by the MCP server
- ❌ No direct calls to AI services from the MCP server
- ✅ Leverages connected LLM's built-in web search and knowledge
**No Separate AI Service Integration**:
- ❌ No additional calls to OpenAI, Anthropic, or other AI services
- ❌ No independent AI processing outside the connected LLM
- ❌ No separate AI models or services
- ✅ Uses only the LLM provided through MCP connection
**No Context Window Management**:
- ❌ Does not extend or manage AI client context windows
- ❌ No conversation history or memory management
- ❌ No cross-session AI context preservation
- ✅ Provides structured project data for AI client consumption
**Human-Only Approval System**:
- ❌ No automated AI-powered document review
- ❌ No AI-based approval recommendations
- ❌ Verbal approval not accepted
- ✅ All approvals require dashboard or VS Code interaction
### What This MCP Excels At
**Leveraging LLM Built-in Capabilities**:
- ✅ Provides structured templates for LLM to fill with intelligent content
- ✅ Supplies project context for LLM analysis and understanding
- ✅ Enables LLM to use its built-in knowledge for best practices
- ✅ Allows LLM to perform web research when generating content
**Structured Workflow Enforcement**:
- ✅ Enforces spec-driven development sequence
- ✅ Template-based document structure for consistent LLM output
- ✅ Workflow validation and blocking
- ✅ Human oversight integration for LLM-generated content
**Intelligent Project Data Management**:
- ✅ Efficient context loading for LLM consumption
- ✅ Real-time file watching and updates
- ✅ Cross-platform path handling
- ✅ Structured project organization that LLM can understand
**Enhanced Developer Experience**:
- ✅ Web dashboard for reviewing LLM-generated content
- ✅ VS Code extension integration
- ✅ Real-time WebSocket updates
- ✅ Comprehensive error handling
## 🎯 Key Concepts
### MCP Tools
The server provides 12 MCP tools for spec-driven development:
- **Workflow Tools**: `spec-workflow-guide`, `steering-guide`
- **Content Tools**: `create-spec-doc`, `create-steering-doc`, `get-template-context`
- **Search Tools**: `get-spec-context`, `get-steering-context`, `spec-list`
- **Status Tools**: `spec-status`, `manage-tasks`
- **Approval Tools**: `request-approval`, `get-approval-status`, `delete-approval`
### File Organization
```
.spec-workflow/
├── specs/ # Specification documents
├── steering/ # Project guidance documents
├── approvals/ # Approval workflow data
└── archive/ # Archived specifications
```
### Workflow Phases
1. **Requirements** → 2. **Design** → 3. **Tasks** → 4. **Implementation**
Each phase requires approval before proceeding to the next.
## 🔧 Development Workflow
### Adding a New MCP Tool
1. Create tool file in `src/tools/`
2. Export tool definition and handler
3. Register in `src/tools/index.ts`
4. Update API documentation
5. Add tests
### Dashboard Development
```bash
# Start dashboard in development mode
npm run dev:dashboard
# Build dashboard assets
npm run build:dashboard
```
### VSCode Extension Development
```bash
cd vscode-extension
npm install
npm run compile
# Press F5 in VSCode to launch extension host
```
## 📚 Documentation Standards
- **Code Examples**: Always include working examples
- **Error Handling**: Document expected error conditions
- **Performance**: Note any performance considerations
- **Security**: Highlight security implications
- **Breaking Changes**: Mark breaking changes clearly
## 🤝 Getting Help
1. **Check the [Troubleshooting Guide](troubleshooting.md)** first
2. **Search existing [GitHub Issues](https://github.com/Pimzino/spec-workflow-mcp/issues)**
3. **Create a new issue** with detailed reproduction steps
4. **Join the community** for real-time support
---
## 📊 Technical Architecture Summary
### Pure MCP Server Design
This project implements a **pure Model Context Protocol (MCP) server** that:
| Aspect | Implementation | Details |
|--------|---------------|----------|
| **AI Integration** | Pure MCP server | Leverages connected LLM's built-in capabilities |
| **Web Research** | LLM built-in capability | LLM performs web search using its built-in features |
| **Context Management** | File-based structure | No LLM context window management |
| **Content Generation** | LLM-powered with templates | LLM fills templates using built-in knowledge & search |
| **Planning Process** | LLM reasoning + workflow validation | LLM plans content, MCP enforces structure |
| **Review System** | Human approval only | Dashboard/VS Code integration for LLM output |
| **Best Practices** | LLM built-in knowledge | LLM applies best practices from its training |
| **External Calls** | NPM version check only | All other capabilities through connected LLM |
### Key Files & Implementation
- **MCP Tools**: `src/tools/*.ts` - 13 tools for workflow management
- **Templates**: `src/markdown/templates/*.md` - Static document structures
- **Approval System**: `src/dashboard/approval-storage.ts` - Human-only review
- **Context Loading**: `src/core/*.ts` - File-based context structuring
- **Web Dashboard**: `src/dashboard_frontend/` - React-based approval UI
### Performance Characteristics
- **Memory Usage**: 50KB templates + 10-100KB per spec context
- **File System**: Local `.spec-workflow/` directory only
- **Network**: Localhost dashboard + NPM version check
- **Scaling**: Linear per project, 50-100 specs recommended
- **Security**: Local-only, no external data transmission
## 📊 Market Analysis & Strategic Insights
### Competitive Landscape Analysis
**Category 1: Autonomous AI Agents (e.g., AutoGPT, LangChain Agents)**
```typescript
interface AutonomousAgents {
capabilities: {
webScraping: "Advanced - Custom scrapers, API integrations";
aiCalls: "Multiple models, specialized AI services";
automation: "Fully autonomous operation";
integrations: "Extensive third-party ecosystem";
};
limitations: {
humanOversight: "Limited or optional";
reliability: "Can go off-track or produce errors";
complexity: "Complex setup, API management";
cost: "High due to multiple AI calls";
};
differentiator: "Full automation vs structured human-guided workflow";
}
```
**Category 2: Development Workflow Tools (e.g., GitHub Copilot, Cursor)**
```typescript
interface DevelopmentTools {
capabilities: {
codeGeneration: "Excellent within editors";
contextAwareness: "Good for code context";
realTimeAssistance: "Integrated development support";
aiPowered: "Built-in LLM capabilities";
};
limitations: {
workflowStructure: "Limited structured spec processes";
documentationFocus: "Code-centric, not spec-driven";
approvalProcess: "No formal review workflows";
projectPlanning: "Limited high-level planning";
};
differentiator: "Code-first vs spec-driven development approach";
}
```
**Category 3: Project Management + AI (e.g., Notion AI, Linear)**
```typescript
interface ProjectManagementAI {
capabilities: {
projectTracking: "Excellent project organization";
collaboration: "Team coordination features";
aiAssistance: "AI-powered content generation";
integration: "Extensive third-party connections";
};
limitations: {
technicalDepth: "Limited technical specification focus";
workflowEnforcement: "Flexible but not enforced";
developerWorkflow: "Not developer-workflow optimized";
codeIntegration: "Limited code context understanding";
};
differentiator: "General project management vs developer-specific workflows";
}
```
### Strategic Market Position
**Spec-Workflow-MCP's Unique Position:**
```typescript
interface MarketPosition {
blueOcean: {
category: "LLM-Enhanced Structured Development Workflows";
uniqueValue: "Human-supervised LLM intelligence with enforced spec-driven process";
targetUser: "Development teams needing structured processes with AI assistance";
};
competitiveAdvantages: {
llmLeverage: "Full LLM power without additional API costs";
humanOversight: "Prevents AI errors through mandatory approval";
structuredProcess: "Enforces proven development methodology";
simplicity: "No complex setup or API key management";
realTimeUI: "Superior user experience with dashboard";
};
marketOpportunities: {
enterpriseAdoption: "Companies wanting AI benefits with human control";
consultingFirms: "Standardized processes across client projects";
startups: "Structured development without overhead";
education: "Teaching proper development workflows";
};
}
```
### Expansion Strategy Insights
**Phase 1: Leverage Core Strengths**
```typescript
interface Phase1Strategy {
buildOnStrengths: {
enhanceHumanOversight: "Advanced approval workflows, review templates";
improveStructure: "Dynamic templates, adaptive workflows";
expandLLMUsage: "Better context utilization, smarter suggestions";
};
addressGaps: {
basicIntegrations: "GitHub, GitLab, Bitbucket connections";
qualityGates: "Optional automated checks before human review";
teamFeatures: "Multi-developer coordination";
};
}
```
**Phase 2: Strategic Differentiation**
```typescript
interface Phase2Strategy {
uniqueCapabilities: {
hybridIntelligence: "Best of LLM automation + human oversight";
contextMastery: "Superior project context understanding";
processExcellence: "Industry-leading structured workflows";
};
competitiveFeatures: {
multiModelSupport: "Support multiple LLM providers";
enterpriseFeatures: "SSO, compliance, audit trails";
aiOrchestration: "Multi-agent coordination while maintaining oversight";
};
}
```
### Strategic Recommendations for Creators
**Immediate Opportunities (0-6 months):**
1. **GitHub Integration**: Leverage LLM to create PRs, analyze codebases
2. **Quality Templates**: Add project-type detection for smarter templates
3. **Team Coordination**: Multi-developer approval workflows
4. **Performance Analytics**: Track spec-to-delivery success rates
**Medium-term Differentiators (6-18 months):**
1. **Hybrid AI Workflows**: Optional automated gates with human oversight
2. **Enterprise Dashboard**: Multi-project management interface
3. **Advanced Integrations**: Jira, Slack, Confluence, CI/CD pipelines
4. **Predictive Analytics**: Project risk analysis using LLM insights
**Long-term Vision (18+ months):**
1. **AI Orchestration Platform**: Multi-agent coordination with human oversight
2. **Industry Templates**: Specialized workflows for different domains
3. **Compliance Integration**: SOX, GDPR, HIPAA workflow templates
4. **Educational Platform**: Teaching structured development at scale
### Market Validation Insights
**This analysis reveals that Spec-Workflow-MCP occupies a unique market position:**
- ✅ **Underserved Market**: Structured development workflows with AI enhancement
- ✅ **Clear Differentiation**: Human oversight + LLM power combination
- ✅ **Expansion Potential**: Multiple clear paths for feature enhancement
- ✅ **Strategic Moat**: Proven workflow methodology that competitors would struggle to replicate
**Last Updated**: December 2024 | **Version**: 0.0.23
```
--------------------------------------------------------------------------------
/docs/technical-documentation/contributing.md:
--------------------------------------------------------------------------------
```markdown
# Contributing Guidelines
> **Welcome!** This guide will help you contribute effectively to the Spec Workflow MCP project.
## 🚀 Quick Start for Contributors
### 1. Setup Development Environment
```bash
# Fork and clone the repository
git clone https://github.com/your-username/spec-workflow-mcp.git
cd spec-workflow-mcp
# Install dependencies
npm install
# Install VS Code extension dependencies (optional)
cd vscode-extension
npm install
cd ..
# Build everything to verify setup
npm run build
```
### 2. Development Workflow
```bash
# Start MCP server in development mode
npm run dev
# In another terminal, start dashboard
npm run dev:dashboard
# Make your changes
# Test thoroughly
# Create pull request
```
## 🎯 How to Contribute
### Areas Where We Need Help
**🔧 Core Features**
- New MCP tools and functionality
- Performance optimizations
- Cross-platform compatibility improvements
**📱 Dashboard & UI**
- New dashboard features
- UI/UX improvements
- Accessibility enhancements
**📚 Documentation**
- Code examples and tutorials
- API documentation improvements
- Translation to other languages
**🧪 Testing**
- Unit test coverage
- Integration test scenarios
- Manual testing on different platforms
**🐛 Bug Fixes**
- Reported issues in GitHub
- Edge cases and error handling
- Performance bottlenecks
## 📋 Contribution Types
### 1. Bug Reports
**Before Creating an Issue**:
- Search existing issues first
- Try the [troubleshooting guide](troubleshooting.md)
- Test with the latest version
**Good Bug Report Template**:
```markdown
## Bug Description
Brief description of the issue
## Environment
- OS: [Windows 11 / macOS 14 / Ubuntu 22.04]
- Node.js: [version]
- MCP Client: [Claude Desktop / Cursor / etc.]
## Steps to Reproduce
1. Step one
2. Step two
3. Step three
## Expected Behavior
What should happen
## Actual Behavior
What actually happens
## Additional Context
- Error messages
- Screenshots
- Logs
```
### 2. Feature Requests
**Good Feature Request Template**:
```markdown
## Feature Description
Clear description of the proposed feature
## Problem It Solves
What problem does this address?
## Proposed Solution
How should it work?
## Alternatives Considered
Other approaches you've considered
## Implementation Ideas
Any thoughts on how to implement this
```
### 3. Code Contributions
#### Pull Request Process
1. **Fork** the repository
2. **Create** a feature branch: `git checkout -b feature/my-feature`
3. **Make** your changes following our coding standards
4. **Test** your changes thoroughly
5. **Document** new functionality
6. **Submit** a pull request with clear description
#### Pull Request Template
```markdown
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
- [ ] Unit tests pass
- [ ] Manual testing completed
- [ ] Cross-platform tested (if applicable)
## Documentation
- [ ] Code is documented
- [ ] README updated (if needed)
- [ ] API docs updated (if needed)
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] No merge conflicts
```
## 🎨 Coding Standards
### TypeScript Guidelines
**File Organization**:
```typescript
// 1. External library imports
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { readFile } from 'fs/promises';
// 2. Internal imports
import { ToolContext, ToolResponse } from '../types.js';
import { PathUtils } from '../core/path-utils.js';
// 3. Type definitions
interface LocalInterface {
// ...
}
// 4. Constants
const CONSTANTS = {
// ...
};
// 5. Main implementation
export class MyClass {
// ...
}
```
**Function Structure**:
```typescript
/**
* Brief description of what the function does
* @param param1 Description of parameter
* @param param2 Description of parameter
* @returns Description of return value
*/
export async function myFunction(
param1: string,
param2: number
): Promise<MyReturnType> {
// Input validation
if (!param1) {
throw new Error('param1 is required');
}
try {
// Main logic
const result = await doSomething(param1, param2);
return result;
} catch (error: any) {
// Error handling
throw new Error(`Operation failed: ${error.message}`);
}
}
```
**Error Handling Pattern**:
```typescript
// MCP Tool error handling
export async function myToolHandler(args: any, context: ToolContext): Promise<ToolResponse> {
try {
// Validation
const { requiredParam } = args;
if (!requiredParam) {
return {
success: false,
message: 'requiredParam is required',
nextSteps: ['Provide the required parameter']
};
}
// Implementation
const result = await doWork(requiredParam);
return {
success: true,
message: 'Operation completed successfully',
data: result,
nextSteps: ['Next recommended action']
};
} catch (error: any) {
return {
success: false,
message: `Operation failed: ${error.message}`,
nextSteps: [
'Check input parameters',
'Verify file permissions',
'Try again or contact support'
]
};
}
}
```
### React Component Guidelines
**Component Structure**:
```typescript
// src/dashboard_frontend/src/components/MyComponent.tsx
import React, { useState, useEffect } from 'react';
interface MyComponentProps {
data: DataType[];
onAction: (item: DataType) => void;
className?: string;
}
export default function MyComponent({
data,
onAction,
className = ''
}: MyComponentProps) {
const [localState, setLocalState] = useState<StateType>({});
useEffect(() => {
// Side effects
}, [data]);
const handleClick = (item: DataType) => {
// Event handlers
onAction(item);
};
return (
<div className={`base-styles ${className}`}>
{data.map(item => (
<div key={item.id} onClick={() => handleClick(item)}>
{item.name}
</div>
))}
</div>
);
}
```
**Styling Guidelines**:
```typescript
// Use Tailwind CSS classes
<div className="p-4 bg-white dark:bg-gray-800 rounded-lg shadow-md">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Title
</h2>
</div>
// Custom CSS only when Tailwind is insufficient
// Add to src/modules/theme/theme.css
```
### File and Directory Naming
```
// Files
kebab-case.ts ✅ Good
PascalCase.ts ❌ Avoid
snake_case.ts ❌ Avoid
// Directories
kebab-case/ ✅ Good
PascalCase/ ❌ Avoid (except React components)
snake_case/ ❌ Avoid
// React Components
MyComponent.tsx ✅ Good (PascalCase for components)
my-component.tsx ❌ Avoid
// MCP Tools
my-tool.ts ✅ Good
myTool.ts ❌ Avoid
```
## 🧪 Testing Guidelines
### Manual Testing Checklist
**Before Submitting PR**:
- [ ] MCP server starts without errors
- [ ] Dashboard loads and displays data
- [ ] WebSocket connections work
- [ ] File changes trigger updates
- [ ] Approval workflow functions
- [ ] Cross-platform compatibility (if applicable)
**Test Scenarios**:
```bash
# 1. Basic MCP server functionality
npm run dev
# Connect AI client and test tools
# 2. Dashboard functionality
npm run dev:dashboard
# Test all pages and features
# 3. VS Code extension (if modified)
cd vscode-extension
# Press F5 in VS Code to test
# 4. Build process
npm run clean
npm run build
# Verify dist/ contents
# 5. CLI interface
node dist/index.js --help
node dist/index.js --dashboard
```
### Future Testing Framework
**Unit Tests** (planned):
```typescript
// Example test structure
describe('PathUtils', () => {
describe('getSpecPath', () => {
it('should create correct spec path', () => {
const result = PathUtils.getSpecPath('/project', 'my-spec');
expect(result).toBe('/project/.spec-workflow/specs/my-spec');
});
it('should handle special characters', () => {
const result = PathUtils.getSpecPath('/project', 'user-auth');
expect(result).toContain('user-auth');
});
});
});
```
## 📖 Documentation Standards
### Code Documentation
**JSDoc Comments**:
```typescript
/**
* Creates a new specification document following the workflow sequence
*
* @param projectPath - Absolute path to the project root
* @param specName - Feature name in kebab-case (e.g., 'user-authentication')
* @param document - Which document to create: 'requirements' | 'design' | 'tasks'
* @param content - Complete markdown content for the document
* @returns Promise resolving to tool response with file path and next steps
*
* @example
* ```typescript
* const response = await createSpecDoc({
* projectPath: '/my/project',
* specName: 'user-auth',
* document: 'requirements',
* content: '# Requirements\n\n...'
* });
* ```
*
* @throws {Error} When workflow order is violated (e.g., creating design before requirements)
*/
export async function createSpecDoc(...): Promise<ToolResponse> {
// Implementation
}
```
**README Updates**:
- Update main README.md for user-facing changes
- Update technical documentation for developer changes
- Include code examples for new features
### API Documentation
**MCP Tool Documentation**:
```typescript
export const myNewToolTool: Tool = {
name: 'my-new-tool',
description: `Brief description of what this tool does.
# Instructions
When to use this tool and how it fits in the workflow.
# Parameters
- param1: Description and format
- param2: Description and constraints
# Example Usage
Concrete example of how to use this tool.`,
inputSchema: {
// JSON Schema
}
};
```
## 🔄 Development Workflow
### Branch Strategy
```bash
# Main branches
main # Stable release code
develop # Integration branch for features
# Feature branches
feature/add-new-tool # New features
bugfix/fix-approval # Bug fixes
docs/update-api # Documentation updates
chore/update-deps # Maintenance tasks
```
### Commit Message Format
```bash
# Format: type(scope): description
feat(tools): add new spec validation tool
fix(dashboard): resolve WebSocket connection issues
docs(api): update MCP tool documentation
chore(deps): update TypeScript to 5.3.0
refactor(parser): simplify task parsing logic
# Types: feat, fix, docs, style, refactor, test, chore
# Scope: tools, dashboard, core, docs, extension
```
### Release Process
**Version Bumping**:
```bash
# Patch release (bug fixes)
npm version patch
# Minor release (new features)
npm version minor
# Major release (breaking changes)
npm version major
```
**Pre-release Checklist**:
- [ ] All tests pass
- [ ] Documentation updated
- [ ] CHANGELOG.md updated
- [ ] Version bumped
- [ ] Build successful
- [ ] Manual testing completed
## 🤝 Community Guidelines
### Code of Conduct
**Our Standards**:
- **Be Respectful** - Treat everyone with respect and kindness
- **Be Inclusive** - Welcome contributors from all backgrounds
- **Be Constructive** - Provide helpful feedback and suggestions
- **Be Patient** - Remember that everyone is learning
**Unacceptable Behavior**:
- Harassment or discrimination
- Trolling or inflammatory comments
- Personal attacks
- Publishing private information
### Getting Help
**For Contributors**:
1. **Read this guide** and linked documentation
2. **Search existing issues** and discussions
3. **Ask in GitHub Discussions** for general questions
4. **Create an issue** for specific problems
5. **Join community channels** (if available)
**For Maintainers**:
- Respond to issues and PRs promptly
- Provide constructive feedback
- Help newcomers get started
- Maintain welcoming environment
## 🏆 Recognition
### Contributors
Contributors are recognized in:
- GitHub contributors list
- CHANGELOG.md for significant contributions
- README.md acknowledgments section
### Types of Contributions
**All contributions are valued**:
- 💻 **Code** - Features, bug fixes, improvements
- 📖 **Documentation** - Guides, examples, translations
- 🐛 **Testing** - Bug reports, test cases, QA
- 💡 **Ideas** - Feature requests, design feedback
- 🎨 **Design** - UI/UX improvements, icons, graphics
- 📢 **Community** - Helping other users, spreading the word
---
**Thank you for contributing to Spec Workflow MCP!** 🎉
Every contribution, no matter how small, helps make this project better for everyone.
---
**Next**: [Testing Guide →](testing.md)
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/vite-env.d.ts:
--------------------------------------------------------------------------------
```typescript
/// <reference types="vite/client" />
declare module "*.css" {
const content: any;
export default content;
}
```
--------------------------------------------------------------------------------
/vscode-extension/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.extension.json" }
]
}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/lib/utils.ts:
--------------------------------------------------------------------------------
```typescript
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
--------------------------------------------------------------------------------
/containers/example.mcp.json:
--------------------------------------------------------------------------------
```json
{
"mcpServers": {
"spec-workflow": {
"command": "npx",
"args": [
"-y",
"@pimzino/spec-workflow-mcp@latest",
"/path/to/your/project"
]
}
}
}
```
--------------------------------------------------------------------------------
/vscode-extension/.vscode/extensions.json:
--------------------------------------------------------------------------------
```json
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": ["dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner"]
}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/index.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spec Dashboard</title>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
```
--------------------------------------------------------------------------------
/containers/docker-compose.yml:
--------------------------------------------------------------------------------
```yaml
services:
spec-workflow-mcp:
build:
context: ..
dockerfile: containers/Dockerfile
ports:
- "${DASHBOARD_PORT:-5000}:${DASHBOARD_PORT:-5000}"
volumes:
- "${SPEC_WORKFLOW_PATH:-./workspace}/.spec-workflow:/workspace/.spec-workflow:rw"
environment:
- DASHBOARD_PORT=${DASHBOARD_PORT:-5000}
restart: unless-stopped
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/modules/approvals/colors.ts:
--------------------------------------------------------------------------------
```typescript
export function hexToColorObject(hex: string) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
const bg = `rgba(${r}, ${g}, ${b}, 0.3)`;
const border = hex;
const name = hex.toLowerCase();
return { bg, border, name };
}
export function isValidHex(hex: string) {
return /^#[0-9A-Fa-f]{6}$/.test(hex);
}
```
--------------------------------------------------------------------------------
/src/prompts/types.ts:
--------------------------------------------------------------------------------
```typescript
import { Prompt, PromptMessage, PromptArgument } from '@modelcontextprotocol/sdk/types.js';
import { ToolContext } from '../types.js';
export interface PromptHandler {
(args: Record<string, any>, context: ToolContext): Promise<PromptMessage[]>;
}
export interface PromptDefinition {
prompt: Prompt;
handler: PromptHandler;
}
export interface PromptResponse {
messages: PromptMessage[];
}
```
--------------------------------------------------------------------------------
/vscode-extension/components.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/webview/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/index.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spec Workflow Dashboard</title>
<style>
/* Prevent FOUC */
body {
visibility: hidden;
}
body.loaded {
visibility: visible;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/main.tsx"></script>
</body>
</html>
```
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
```typescript
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
globals: true,
include: ['src/**/*.{test,spec}.{js,ts}'],
exclude: ['src/dashboard_frontend/**/*', 'node_modules/**/*'],
coverage: {
reporter: ['text', 'json', 'html'],
exclude: [
'src/dashboard_frontend/**',
'dist/**',
'**/*.d.ts',
'**/*.config.*',
'**/test/**',
'**/__tests__/**',
]
}
}
});
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/main.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './globals.css';
import './i18n';
// Create root and render app
const container = document.getElementById('root');
if (container) {
const root = ReactDOM.createRoot(container);
root.render(
<React.StrictMode>
<Suspense fallback="Loading...">
<App />
</Suspense>
</React.StrictMode>
);
// Mark body as loaded to prevent FOUC
document.body.classList.add('loaded');
}
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "node16",
"moduleResolution": "node16",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "src/dashboard_frontend/**"]
}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/main.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { Suspense } from 'react';
import { createRoot } from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
import App from './modules/app/App';
import './modules/theme/tailwind.css';
import './modules/theme/theme.css';
import './i18n';
const container = document.getElementById('root');
if (container) {
const root = createRoot(container);
root.render(
<React.StrictMode>
<Suspense fallback="loading">
<HashRouter>
<App />
</HashRouter>
</Suspense>
</React.StrictMode>
);
}
```
--------------------------------------------------------------------------------
/vscode-extension/tsconfig.extension.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"lib": ["ES2022"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "Node16",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"sourceMap": true,
"baseUrl": "."
},
"include": [
"src/extension",
"src/extension.ts",
"esbuild.js"
],
"exclude": [
"node_modules"
]
}
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
```yaml
# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json
blank_issues_enabled: false
contact_links:
- name: 💬 Discussion
url: https://github.com/anthropics/claude-code/issues
about: Ask questions, share ideas, or discuss Claude Code and MCP projects
- name: 📖 MCP Documentation
url: https://modelcontextprotocol.io
about: Read about the Model Context Protocol specification and implementation guides
- name: ⚡ Claude Code
url: https://claude.ai/code
about: Official Claude Code documentation and resources
```
--------------------------------------------------------------------------------
/vscode-extension/.vscode/launch.json:
--------------------------------------------------------------------------------
```json
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
}
]
}
```
--------------------------------------------------------------------------------
/vscode-extension/webview-dist/index.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spec Workflow Dashboard</title>
<style>
/* Prevent FOUC */
body {
visibility: hidden;
}
body.loaded {
visibility: visible;
}
</style>
<script type="module" crossorigin src="/main.js"></script>
<link rel="modulepreload" crossorigin href="/i18n.js">
<link rel="stylesheet" crossorigin href="/globals.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/vite.config.ts:
--------------------------------------------------------------------------------
```typescript
import { defineConfig } from 'vite';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import react from '@vitejs/plugin-react';
// Dynamically import Tailwind CSS v4 plugin
async function createConfig() {
const { default: tailwindcss } = await import('@tailwindcss/vite');
return {
plugins: [react(), tailwindcss()],
// Ensure Vite resolves index.html relative to this config file
root: dirname(fileURLToPath(new URL(import.meta.url))),
base: '/',
build: {
outDir: 'dist',
emptyOutDir: true,
},
};
}
export default defineConfig(createConfig());
```
--------------------------------------------------------------------------------
/vscode-extension/.vscode/settings.json:
--------------------------------------------------------------------------------
```json
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false, // set this to true to hide the "out" folder with the compiled JS files
"dist": false // set this to true to hide the "dist" folder with the compiled JS files
},
"search.exclude": {
"out": true, // set this to false to include "out" folder in search results
"dist": true // set this to false to include "dist" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
}
```
--------------------------------------------------------------------------------
/vscode-extension/tsconfig.node.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/components/ui/separator.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils";
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props}
/>
);
}
export { Separator };
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/components/ui/input.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from "react";
import { cn } from "@/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
);
Input.displayName = "Input";
export { Input };
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/comment-modal.html:
--------------------------------------------------------------------------------
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Comment</title>
<link rel="stylesheet" href="./globals.css">
<style>
body {
margin: 0;
padding: 0;
font-family: var(--vscode-font-family), -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
font-size: var(--vscode-font-size, 13px);
line-height: 1.4;
color: var(--vscode-foreground);
background: var(--vscode-editor-background);
}
#root {
width: 100%;
height: 100vh;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="./comment-modal.tsx"></script>
</body>
</html>
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/components/ui/progress.tsx:
--------------------------------------------------------------------------------
```typescript
"use client";
import * as React from "react";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import { cn } from "@/lib/utils";
function Progress({
className,
value,
...props
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
return (
<ProgressPrimitive.Root
data-slot="progress"
className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="bg-primary h-full w-full flex-1 transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
);
}
export { Progress };
```
--------------------------------------------------------------------------------
/vscode-extension/eslint.config.mjs:
--------------------------------------------------------------------------------
```
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
export default [{
files: ["**/*.ts", "**/*.tsx"],
ignores: ["node_modules/**", "webview-dist/**", "dist/**", "out/**", "**/*.d.ts", "**/webview-dist/**"],
}, {
plugins: {
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
"@typescript-eslint/naming-convention": ["warn", {
selector: "import",
format: ["camelCase", "PascalCase"],
}],
curly: "warn",
eqeqeq: "warn",
"no-throw-literal": "warn",
semi: "warn",
},
}];
```
--------------------------------------------------------------------------------
/vscode-extension/tsconfig.app.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Path mapping */
"baseUrl": ".",
"paths": {
"@/*": ["./src/webview/*"]
},
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/webview"]
}
```
--------------------------------------------------------------------------------
/vscode-extension/webview-dist/comment-modal.html:
--------------------------------------------------------------------------------
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Comment</title>
<style>
body {
margin: 0;
padding: 0;
font-family: var(--vscode-font-family), -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
font-size: var(--vscode-font-size, 13px);
line-height: 1.4;
color: var(--vscode-foreground);
background: var(--vscode-editor-background);
}
#root {
width: 100%;
height: 100vh;
}
</style>
<script type="module" crossorigin src="/comment-modal.js"></script>
<link rel="modulepreload" crossorigin href="/i18n.js">
<link rel="stylesheet" crossorigin href="/globals.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/lib/utils.ts:
--------------------------------------------------------------------------------
```typescript
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function formatDate(dateStr?: string) {
if (!dateStr) {return 'Never';}
return new Date(dateStr).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
export function formatDistanceToNow(dateStr: string) {
const date = new Date(dateStr);
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
if (diffInSeconds < 60) {return 'Just now';}
if (diffInSeconds < 3600) {return `${Math.floor(diffInSeconds / 60)}m ago`;}
if (diffInSeconds < 86400) {return `${Math.floor(diffInSeconds / 3600)}h ago`;}
return `${Math.floor(diffInSeconds / 86400)}d ago`;
}
```
--------------------------------------------------------------------------------
/containers/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Build stage
FROM node:24-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install all dependencies (including devDependencies for build)
RUN npm ci
# Copy source files
COPY src ./src
COPY scripts ./scripts
COPY tsconfig.json ./tsconfig.json
COPY vitest.config.ts ./vitest.config.ts
# Build the application
RUN npm run build
# Runtime stage
FROM node:24-alpine
WORKDIR /app
# Copy only production files
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
# Copy built application
COPY --from=builder /app/dist ./dist
RUN mkdir -p /workspace
# Change ownership of the app directory to the node user (uid=1000)
RUN chown -R node:node /app /workspace
# Switch to the node user to match host user permissions
USER node
WORKDIR /workspace
EXPOSE 5000
CMD node /app/dist/index.js ${SPEC_WORKFLOW_PATH:-/workspace} --dashboard --port ${DASHBOARD_PORT:-5000}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/modules/theme/HighlightStyles.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { useEffect } from 'react';
import { useTheme } from './ThemeProvider';
const LIGHT_ID = 'hljs-light-theme';
const DARK_ID = 'hljs-dark-theme';
const LIGHT_HREF = 'https://cdn.jsdelivr.net/npm/[email protected]/styles/github.min.css';
const DARK_HREF = 'https://cdn.jsdelivr.net/npm/[email protected]/styles/github-dark.min.css';
function ensureLink(id: string, href: string) {
let link = document.getElementById(id) as HTMLLinkElement | null;
if (!link) {
link = document.createElement('link');
link.id = id;
link.rel = 'stylesheet';
link.href = href;
document.head.appendChild(link);
}
return link;
}
export function HighlightStyles() {
const { theme } = useTheme();
useEffect(() => {
const light = ensureLink(LIGHT_ID, LIGHT_HREF);
const dark = ensureLink(DARK_ID, DARK_HREF);
if (theme === 'dark') {
light.disabled = true;
dark.disabled = false;
} else {
light.disabled = false;
dark.disabled = true;
}
}, [theme]);
return null;
}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/modules/theme/ThemeProvider.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import './tailwind.css';
type Theme = 'light' | 'dark';
type ThemeContextType = {
theme: Theme;
toggleTheme: () => void;
};
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>(() => {
const saved = localStorage.getItem('theme');
return (saved as Theme) || 'dark';
});
useEffect(() => {
document.documentElement.classList.toggle('dark', theme === 'dark');
localStorage.setItem('theme', theme);
}, [theme]);
const value = useMemo(
() => ({ theme, toggleTheme: () => setTheme((t) => (t === 'dark' ? 'light' : 'dark')) }),
[theme]
);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
export function useTheme(): ThemeContextType {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/modules/theme/tailwind.css:
--------------------------------------------------------------------------------
```css
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@variant dark (.dark &);
:root {
color-scheme: light dark;
}
.card {
@apply bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-sm;
}
.card-hover {
@apply transition-transform duration-200 hover:-translate-y-0.5 hover:shadow-md;
}
.btn {
@apply inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-transparent bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-700 transition;
}
.btn-secondary {
@apply inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 transition;
}
.muted { @apply text-gray-500 dark:text-gray-400; }
/* Drag and Drop utilities */
.draggable {
@apply touch-none select-none;
}
.dragging {
@apply cursor-grabbing opacity-75 scale-105 rotate-1 z-50;
}
.drag-overlay {
@apply pointer-events-none;
}
.drop-zone {
@apply transition-colors duration-200;
}
.drop-zone-active {
@apply ring-2 ring-blue-400 bg-blue-50 dark:bg-blue-900/10;
}
```
--------------------------------------------------------------------------------
/vscode-extension/esbuild.js:
--------------------------------------------------------------------------------
```javascript
const esbuild = require("esbuild");
const production = process.argv.includes('--production');
const watch = process.argv.includes('--watch');
/**
* @type {import('esbuild').Plugin}
*/
const esbuildProblemMatcherPlugin = {
name: 'esbuild-problem-matcher',
setup(build) {
build.onStart(() => {
console.log('[watch] build started');
});
build.onEnd((result) => {
result.errors.forEach(({ text, location }) => {
console.error(`✘ [ERROR] ${text}`);
console.error(` ${location.file}:${location.line}:${location.column}:`);
});
console.log('[watch] build finished');
});
},
};
async function main() {
const ctx = await esbuild.context({
entryPoints: [
'src/extension.ts'
],
bundle: true,
format: 'cjs',
minify: production,
sourcemap: !production,
sourcesContent: false,
platform: 'node',
outfile: 'dist/extension.js',
external: ['vscode'],
logLevel: 'silent',
plugins: [
/* add to the end of plugins array */
esbuildProblemMatcherPlugin,
],
});
if (watch) {
await ctx.watch();
} else {
await ctx.rebuild();
await ctx.dispose();
}
}
main().catch(e => {
console.error(e);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/modules/notifications/VolumeControl.module.css:
--------------------------------------------------------------------------------
```css
/* Slider styles for VolumeControl component */
.slider {
-webkit-appearance: none;
appearance: none;
width: 80px;
height: 4px;
border-radius: 2px;
background: #e5e7eb;
outline: none;
cursor: pointer;
transition: background 0.3s;
}
.dark .slider {
background: #4b5563;
}
/* Webkit browsers (Chrome, Safari, Edge) */
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: #2563eb;
cursor: pointer;
border: 2px solid #ffffff;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
transition: background 0.2s;
margin-top: -6px;
}
.slider::-webkit-slider-thumb:hover {
background: #1d4ed8;
}
/* Firefox */
.slider::-moz-range-thumb {
height: 16px;
width: 16px;
border-radius: 50%;
background: #2563eb;
cursor: pointer;
border: 2px solid #ffffff;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
transition: background 0.2s;
}
.slider::-moz-range-thumb:hover {
background: #1d4ed8;
}
/* Track styles */
.slider::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
border-radius: 2px;
}
.slider::-moz-range-track {
width: 100%;
height: 4px;
cursor: pointer;
border-radius: 2px;
}
```
--------------------------------------------------------------------------------
/src/markdown/templates/requirements-template.md:
--------------------------------------------------------------------------------
```markdown
# Requirements Document
## Introduction
[Provide a brief overview of the feature, its purpose, and its value to users]
## Alignment with Product Vision
[Explain how this feature supports the goals outlined in product.md]
## Requirements
### Requirement 1
**User Story:** As a [role], I want [feature], so that [benefit]
#### Acceptance Criteria
1. WHEN [event] THEN [system] SHALL [response]
2. IF [precondition] THEN [system] SHALL [response]
3. WHEN [event] AND [condition] THEN [system] SHALL [response]
### Requirement 2
**User Story:** As a [role], I want [feature], so that [benefit]
#### Acceptance Criteria
1. WHEN [event] THEN [system] SHALL [response]
2. IF [precondition] THEN [system] SHALL [response]
## Non-Functional Requirements
### Code Architecture and Modularity
- **Single Responsibility Principle**: Each file should have a single, well-defined purpose
- **Modular Design**: Components, utilities, and services should be isolated and reusable
- **Dependency Management**: Minimize interdependencies between modules
- **Clear Interfaces**: Define clean contracts between components and layers
### Performance
- [Performance requirements]
### Security
- [Security requirements]
### Reliability
- [Reliability requirements]
### Usability
- [Usability requirements]
```
--------------------------------------------------------------------------------
/vscode-extension/package.nls.json:
--------------------------------------------------------------------------------
```json
{
"displayName": "Spec Workflow MCP",
"description": "VSCode extension for Spec-Workflow-MCP with integrated dashboard",
"command.openDashboard": "Open Spec Workflow Dashboard",
"command.refreshData": "Refresh Data",
"command.openSpec": "Open Spec",
"command.approve": "Approve",
"command.reject": "Reject",
"command.requestRevision": "Request Revision",
"command.addComment": "Add Comment",
"command.editComment": "Edit Comment",
"command.deleteComment": "Delete Comment",
"command.showApprovalActions": "Approval Actions",
"view.dashboard": "Dashboard",
"view.containerTitle": "Spec Workflow",
"config.title": "Spec Workflow",
"config.sounds.enabled.description": "Enable sound notifications for spec workflow events",
"config.sounds.volume.description": "Volume level for sound notifications (0.0 to 1.0)",
"config.sounds.approvalSound.description": "Play sound when new approval requests are pending",
"config.sounds.taskCompletionSound.description": "Play sound when tasks are completed",
"config.language.description": "Language for the extension interface",
"config.language.auto.description": "Auto-detect based on VS Code settings",
"config.language.en.description": "English",
"config.language.ja.description": "Japanese (日本語)",
"config.language.zh.description": "Chinese (中文)"
}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/types.ts:
--------------------------------------------------------------------------------
```typescript
export interface AutomationJob {
id: string;
name: string;
type: 'cleanup-approvals' | 'cleanup-specs' | 'cleanup-archived-specs';
enabled: boolean;
config: {
daysOld: number;
};
schedule: string;
lastRun?: string;
nextRun?: string;
createdAt: string;
}
export interface ImplementationLogEntry {
id: string;
taskId: string;
timestamp: string;
summary: string;
filesModified: string[];
filesCreated: string[];
statistics: {
linesAdded: number;
linesRemoved: number;
filesChanged: number;
};
artifacts: {
apiEndpoints?: Array<{
method: string;
path: string;
purpose: string;
requestFormat?: string;
responseFormat?: string;
location: string;
}>;
components?: Array<{
name: string;
type: string;
purpose: string;
location: string;
props?: string;
exports?: string[];
}>;
functions?: Array<{
name: string;
purpose: string;
location: string;
signature?: string;
isExported: boolean;
}>;
classes?: Array<{
name: string;
purpose: string;
location: string;
methods?: string[];
isExported: boolean;
}>;
integrations?: Array<{
description: string;
frontendComponent: string;
backendEndpoint: string;
dataFlow: string;
}>;
};
}
```
--------------------------------------------------------------------------------
/vscode-extension/.vscode/tasks.json:
--------------------------------------------------------------------------------
```json
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"label": "watch",
"dependsOn": [
"npm: watch:tsc",
"npm: watch:esbuild"
],
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "watch:esbuild",
"group": "build",
"problemMatcher": "$esbuild-watch",
"isBackground": true,
"label": "npm: watch:esbuild",
"presentation": {
"group": "watch",
"reveal": "never"
}
},
{
"type": "npm",
"script": "watch:tsc",
"group": "build",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"label": "npm: watch:tsc",
"presentation": {
"group": "watch",
"reveal": "never"
}
},
{
"type": "npm",
"script": "watch-tests",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never",
"group": "watchers"
},
"group": "build"
},
{
"label": "tasks: watch-tests",
"dependsOn": [
"npm: watch",
"npm: watch-tests"
],
"problemMatcher": []
}
]
}
```
--------------------------------------------------------------------------------
/src/markdown/templates/product-template.md:
--------------------------------------------------------------------------------
```markdown
# Product Overview
## Product Purpose
[Describe the core purpose of this product/project. What problem does it solve?]
## Target Users
[Who are the primary users of this product? What are their needs and pain points?]
## Key Features
[List the main features that deliver value to users]
1. **Feature 1**: [Description]
2. **Feature 2**: [Description]
3. **Feature 3**: [Description]
## Business Objectives
[What are the business goals this product aims to achieve?]
- [Objective 1]
- [Objective 2]
- [Objective 3]
## Success Metrics
[How will we measure the success of this product?]
- [Metric 1]: [Target]
- [Metric 2]: [Target]
- [Metric 3]: [Target]
## Product Principles
[Core principles that guide product decisions]
1. **[Principle 1]**: [Explanation]
2. **[Principle 2]**: [Explanation]
3. **[Principle 3]**: [Explanation]
## Monitoring & Visibility (if applicable)
[How do users track progress and monitor the system?]
- **Dashboard Type**: [e.g., Web-based, CLI, Desktop app]
- **Real-time Updates**: [e.g., WebSocket, polling, push notifications]
- **Key Metrics Displayed**: [What information is most important to surface]
- **Sharing Capabilities**: [e.g., read-only links, exports, reports]
## Future Vision
[Where do we see this product evolving in the future?]
### Potential Enhancements
- **Remote Access**: [e.g., Tunnel features for sharing dashboards with stakeholders]
- **Analytics**: [e.g., Historical trends, performance metrics]
- **Collaboration**: [e.g., Multi-user support, commenting]
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/components/LogStatsPanel.tsx:
--------------------------------------------------------------------------------
```typescript
import { useTranslation } from 'react-i18next';
import { Card, CardContent } from './ui/card';
interface LogStatsPanelProps {
stats: {
totalEntries: number;
totalLinesAdded: number;
totalLinesRemoved: number;
totalFilesChanged: number;
} | null;
}
export function LogStatsPanel({ stats }: LogStatsPanelProps) {
const { t } = useTranslation();
if (!stats) {
return null;
}
return (
<Card className="mb-6">
<CardContent className="p-4">
<div className="grid grid-cols-4 gap-4">
<div className="text-center">
<div className="text-2xl font-bold">{stats.totalEntries}</div>
<div className="text-xs text-muted-foreground">{t('logs.stats.totalEntries')}</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-green-600">{stats.totalLinesAdded}</div>
<div className="text-xs text-muted-foreground">{t('logs.stats.linesAdded')}</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-red-600">{stats.totalLinesRemoved}</div>
<div className="text-xs text-muted-foreground">{t('logs.stats.linesRemoved')}</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-purple-600">{stats.totalFilesChanged}</div>
<div className="text-xs text-muted-foreground">{t('logs.stats.filesChanged')}</div>
</div>
</div>
</CardContent>
</Card>
);
}
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation.yml:
--------------------------------------------------------------------------------
```yaml
name: 📚 Documentation
description: Report issues with documentation or suggest improvements
title: "[Docs]: "
labels: ["documentation", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Help us improve the documentation!
- type: dropdown
id: issue-type
attributes:
label: Issue Type
description: What kind of documentation issue is this?
options:
- "Error or typo"
- "Unclear explanation"
- "Missing information"
- "Outdated information"
- "New documentation needed"
- "Better examples needed"
- "Other"
validations:
required: true
- type: textarea
id: location
attributes:
label: Where is the issue?
description: Which documentation has the issue?
placeholder: |
- File: README.md, etc.
- Section: Installation, Usage, etc.
- URL: If online documentation
validations:
required: true
- type: textarea
id: issue-description
attributes:
label: What's the problem?
description: Describe the documentation issue
placeholder: What's wrong or missing?
validations:
required: true
- type: textarea
id: suggested-improvement
attributes:
label: How should it be improved?
description: What would make it better?
placeholder: Your suggested improvement...
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Any other details that would help?
placeholder: Add any other context here...
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/components/ui/badge.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span";
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
}
export { Badge, badgeVariants };
```
--------------------------------------------------------------------------------
/vscode-extension/src/extension/utils/logger.ts:
--------------------------------------------------------------------------------
```typescript
import * as vscode from 'vscode';
export class Logger {
private outputChannel: vscode.OutputChannel;
constructor(outputChannel: vscode.OutputChannel) {
this.outputChannel = outputChannel;
}
private formatMessage(level: string, message: string, data?: any): string {
const timestamp = new Date().toISOString();
let formatted = `[${timestamp}] ${level}: ${message}`;
if (data !== undefined) {
if (typeof data === 'object') {
formatted += `\n${JSON.stringify(data, null, 2)}`;
} else {
formatted += ` ${data}`;
}
}
return formatted;
}
log(message: string, data?: any) {
const formatted = this.formatMessage('INFO', message, data);
this.outputChannel.appendLine(formatted);
}
error(message: string, data?: any) {
const formatted = this.formatMessage('ERROR', message, data);
this.outputChannel.appendLine(formatted);
}
warn(message: string, data?: any) {
const formatted = this.formatMessage('WARN', message, data);
this.outputChannel.appendLine(formatted);
}
debug(message: string, data?: any) {
const formatted = this.formatMessage('DEBUG', message, data);
this.outputChannel.appendLine(formatted);
}
separator(title?: string) {
const line = '='.repeat(60);
if (title) {
const padding = Math.max(0, 60 - title.length - 4);
const leftPad = Math.floor(padding / 2);
const rightPad = padding - leftPad;
this.outputChannel.appendLine(`${'='.repeat(leftPad)} ${title} ${'='.repeat(rightPad)}`);
} else {
this.outputChannel.appendLine(line);
}
}
show() {
this.outputChannel.show();
}
}
```
--------------------------------------------------------------------------------
/src/dashboard/public/claude-icon-dark.svg:
--------------------------------------------------------------------------------
```
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Claude</title><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#F4A971" fill-rule="nonzero"></path></svg>
```
--------------------------------------------------------------------------------
/src/dashboard/public/claude-icon.svg:
--------------------------------------------------------------------------------
```
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Claude</title><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#D97757" fill-rule="nonzero"></path></svg>
```
--------------------------------------------------------------------------------
/containers/DOCKER_USAGE.md:
--------------------------------------------------------------------------------
```markdown
# Example MCP Configuration with Docker Dashboard
This directory contains an example MCP server configuration (`example.mcp.json`)
for use with the Docker-hosted dashboard.
## Architecture
The recommended setup is:
- **Dashboard**: Runs in Docker (using docker-compose.yml)
- **MCP Servers**: Run on host machine via npx (using example.mcp.json)
## Quick Start
1. **Start the Dashboard in Docker:**
```bash
cd containers
docker-compose up -d
```
Dashboard will be at: http://localhost:5000
2. **Configure MCP Servers:**
Use the configuration from `example.mcp.json` in your MCP client config:
```json
{
"mcpServers": {
"spec-workflow": {
"command": "npx",
"args": ["-y", "@pimzino/spec-workflow-mcp@latest", "/path/to/your/project"]
}
}
}
```
3. **Start Your MCP Client:**
The MCP servers will automatically connect to the dashboard at port 5000.
## Why This Architecture?
- **Dashboard in Docker**: Provides isolation and easy deployment
- **MCP Servers on Host**: Allows direct file system access to your projects
- **Automatic Connection**: MCP servers auto-detect and connect to dashboard
## Alternative: Everything in Docker
If you need to run MCP servers in Docker (not recommended for most users):
- You'll need to create a custom setup with network bridges
- File system access becomes more complex
- The current setup (dashboard in Docker, MCP on host) is simpler and more flexible
## See Also
- [Docker Setup Guide](README.md) - Complete Docker documentation
- [Main README](../README.md) - General setup and usage
- [Configuration Guide](../docs/CONFIGURATION.md) - Advanced configuration options
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/hooks/useVSCodeTheme.ts:
--------------------------------------------------------------------------------
```typescript
import { useState, useEffect } from 'react';
type VSCodeTheme = 'light' | 'dark' | 'high-contrast';
/**
* Custom hook to detect and track VS Code theme changes
* VS Code automatically adds theme classes to the body element:
* - 'vscode-light' for light themes
* - 'vscode-dark' for dark themes
* - 'vscode-high-contrast' for high contrast themes
*/
export function useVSCodeTheme(): VSCodeTheme {
const [theme, setTheme] = useState<VSCodeTheme>(() => {
// Initial theme detection
const body = document.body;
if (body.classList.contains('vscode-high-contrast')) {
return 'high-contrast';
}
if (body.classList.contains('vscode-dark')) {
return 'dark';
}
return 'light';
});
useEffect(() => {
const detectTheme = (): VSCodeTheme => {
const body = document.body;
if (body.classList.contains('vscode-high-contrast')) {
return 'high-contrast';
}
if (body.classList.contains('vscode-dark')) {
return 'dark';
}
return 'light';
};
// Update theme on initial mount
setTheme(detectTheme());
// Create observer to watch for theme changes
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const newTheme = detectTheme();
setTheme(newTheme);
}
});
});
// Start observing body class changes
observer.observe(document.body, {
attributes: true,
attributeFilter: ['class']
});
// Cleanup observer on unmount
return () => {
observer.disconnect();
};
}, []);
return theme;
}
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
```yaml
name: ✨ Feature Request
description: Suggest a new feature or enhancement for the Spec-Driven Workflow MCP Server
title: "[Feature]: "
labels: ["enhancement", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for suggesting a feature! Help us understand what you'd like to see.
- type: textarea
id: feature-description
attributes:
label: Feature Description
description: What feature would you like to see?
placeholder: Describe the feature you'd like...
validations:
required: true
- type: textarea
id: problem-statement
attributes:
label: Problem or Use Case
description: What problem would this solve or what would you use it for?
placeholder: |
Describe the problem or use case:
- What are you trying to accomplish?
- What's currently difficult or missing?
validations:
required: true
- type: textarea
id: proposed-solution
attributes:
label: How should it work?
description: Describe how you envision this feature working
placeholder: How would you like this feature to work?
- type: dropdown
id: feature-area
attributes:
label: Feature Area
description: Which area would this affect?
options:
- MCP Tools
- Web Dashboard
- VSCode Extension
- Spec Workflow
- Bug Workflow
- Templates
- Documentation
- Other
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Anything else that would help us understand this request?
placeholder: Add any other details, examples, or context here...
```
--------------------------------------------------------------------------------
/vscode-extension/icons/activity-bar-icon.svg:
--------------------------------------------------------------------------------
```
<svg xmlns="http://www.w3.org/2000/svg"
width="96" height="96" viewBox="0 0 96 96"
preserveAspectRatio="xMidYMid meet"
fill="none" stroke="currentColor" stroke-width="0.75"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<title>Flow diagram on a page</title>
<style>*{vector-effect:non-scaling-stroke}</style>
<g transform="scale(4)">
<!-- Page with folded corner -->
<path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z"/>
<path d="M14 2v6h6"/>
<!-- Nodes (precisely aligned) -->
<!-- Start: circle center (9,7.5), r=1.6 -> bottom tangent at y=9.1 -->
<circle cx="9" cy="7.5" r="1.6"/>
<!-- Process: rect from (6,11.5) size 6x3.5; center y=13.25; right mid at (12,13.25) -->
<rect x="6" y="11.5" width="6" height="3.5" rx="0.6"/>
<!-- Decision: diamond centered at (16.25,13.25), touching (16.25,11.5) top,
(18,13.25) right, (16.25,15) bottom, (14.5,13.25) left -->
<polygon points="16.25,11.5 18,13.25 16.25,15 14.5,13.25"/>
<!-- Connectors (end exactly at tangents/edges) -->
<!-- From circle bottom (9,9.1) to process top center (9,11.5) -->
<line x1="9" y1="9.1" x2="9" y2="11.5"/>
<!-- Arrowhead into the process box -->
<polyline points="8.55,11.0 9,11.5 9.45,11.0"/>
<!-- From process right mid (12,13.25) to diamond left point (14.5,13.25) -->
<line x1="12" y1="13.25" x2="14.5" y2="13.25"/>
<!-- Arrowhead into the diamond -->
<polyline points="14.0,12.8 14.5,13.25 14.0,13.7"/>
<!-- Optional: from diamond bottom point (16.25,15) down a bit to imply continuation -->
<line x1="16.25" y1="15" x2="16.25" y2="16.75"/>
<polyline points="15.8,16.25 16.25,16.75 16.7,16.25"/>
</g>
</svg>
```
--------------------------------------------------------------------------------
/src/prompts/index.ts:
--------------------------------------------------------------------------------
```typescript
import { Prompt, PromptMessage, ListPromptsResult, GetPromptResult } from '@modelcontextprotocol/sdk/types.js';
import { ToolContext } from '../types.js';
import { PromptDefinition, PromptHandler } from './types.js';
// Import individual prompt definitions
import { createSpecPrompt } from './create-spec.js';
import { createSteeringDocPrompt } from './create-steering-doc.js';
import { implementTaskPrompt } from './implement-task.js';
import { specStatusPrompt } from './spec-status.js';
import { injectSpecWorkflowGuidePrompt } from './inject-spec-workflow-guide.js';
import { injectSteeringGuidePrompt } from './inject-steering-guide.js';
import { refreshTasksPrompt } from './refresh-tasks.js';
// Registry of all prompts
const promptDefinitions: PromptDefinition[] = [
createSpecPrompt,
createSteeringDocPrompt,
implementTaskPrompt,
specStatusPrompt,
injectSpecWorkflowGuidePrompt,
injectSteeringGuidePrompt,
refreshTasksPrompt
];
/**
* Get all registered prompts
*/
export function registerPrompts(): Prompt[] {
return promptDefinitions.map(def => def.prompt);
}
/**
* Handle prompts/list request
*/
export async function handlePromptList(): Promise<ListPromptsResult> {
return {
prompts: registerPrompts()
};
}
/**
* Handle prompts/get request
*/
export async function handlePromptGet(
name: string,
args: Record<string, any> = {},
context: ToolContext
): Promise<GetPromptResult> {
const promptDef = promptDefinitions.find(def => def.prompt.name === name);
if (!promptDef) {
throw new Error(`Prompt not found: ${name}`);
}
try {
const messages = await promptDef.handler(args, context);
return { messages };
} catch (error: any) {
throw new Error(`Failed to generate prompt messages: ${error.message}`);
}
}
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/i18n.ts:
--------------------------------------------------------------------------------
```typescript
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import enTranslation from './locales/en.json';
import jaTranslation from './locales/ja.json';
import zhTranslation from './locales/zh.json';
import esTranslation from './locales/es.json';
import ptTranslation from './locales/pt.json';
import deTranslation from './locales/de.json';
import frTranslation from './locales/fr.json';
import ruTranslation from './locales/ru.json';
import itTranslation from './locales/it.json';
import koTranslation from './locales/ko.json';
import arTranslation from './locales/ar.json';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: {
translation: enTranslation,
},
ja: {
translation: jaTranslation,
},
zh: {
translation: zhTranslation,
},
es: {
translation: esTranslation,
},
pt: {
translation: ptTranslation,
},
de: {
translation: deTranslation,
},
fr: {
translation: frTranslation,
},
ru: {
translation: ruTranslation,
},
it: {
translation: itTranslation,
},
ko: {
translation: koTranslation,
},
ar: {
translation: arTranslation,
},
},
fallbackLng: 'en',
interpolation: {
escapeValue: false, // react already safes from xss
},
debug: true, // Enable debug mode for webview
detection: {
// Configure language detector to check for manual preference first
order: ['localStorage', 'navigator', 'htmlTag'],
lookupLocalStorage: 'spec-workflow-language',
caches: ['localStorage'],
},
});
export default i18n;
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/comment-modal.tsx:
--------------------------------------------------------------------------------
```typescript
import ReactDOM from 'react-dom/client';
import { CommentModal } from '@/components/CommentModal';
import { I18nextProvider } from 'react-i18next';
import i18n from './i18n';
import '@/globals.css';
interface SaveCommentMessage {
command: 'save';
comment: string;
color: string;
}
interface CancelMessage {
command: 'cancel';
}
// Type for webview communication
// type WebviewMessage = SaveCommentMessage | CancelMessage;
// Extend the existing VSCode API interface
declare global {
interface Window {
initialState?: {
selectedText: string;
existingComment?: {
id: string;
text: string;
highlightColor?: {
bg: string;
border: string;
name: string;
};
timestamp: string;
} | null;
};
}
}
const vscode = window.acquireVsCodeApi?.();
function CommentModalApp() {
// Get initial data from webview
const selectedText = window.initialState?.selectedText || i18n.t('commentModal.noTextSelected');
const existingComment = window.initialState?.existingComment || null;
const handleSave = (comment: string, color: string) => {
const message: SaveCommentMessage = {
command: 'save',
comment,
color
};
vscode?.postMessage(message);
};
const handleCancel = () => {
const message: CancelMessage = {
command: 'cancel'
};
vscode?.postMessage(message);
};
return (
<I18nextProvider i18n={i18n}>
<CommentModal
selectedText={selectedText}
existingComment={existingComment}
onSave={handleSave}
onCancel={handleCancel}
/>
</I18nextProvider>
);
}
// Mount the React app
const container = document.getElementById('root');
if (container) {
const root = ReactDOM.createRoot(container);
root.render(<CommentModalApp />);
}
```
--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------
```typescript
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { specWorkflowGuideTool, specWorkflowGuideHandler } from './spec-workflow-guide.js';
import { specStatusTool, specStatusHandler } from './spec-status.js';
import { steeringGuideTool, steeringGuideHandler } from './steering-guide.js';
import { approvalsTool, approvalsHandler } from './approvals.js';
import { logImplementationTool, logImplementationHandler } from './log-implementation.js';
import { ToolContext, ToolResponse, MCPToolResponse, toMCPResponse } from '../types.js';
export function registerTools(): Tool[] {
return [
specWorkflowGuideTool,
steeringGuideTool,
specStatusTool,
approvalsTool,
logImplementationTool
];
}
export async function handleToolCall(name: string, args: any, context: ToolContext): Promise<MCPToolResponse> {
let response: ToolResponse;
let isError = false;
try {
switch (name) {
case 'spec-workflow-guide':
response = await specWorkflowGuideHandler(args, context);
break;
case 'steering-guide':
response = await steeringGuideHandler(args, context);
break;
case 'spec-status':
response = await specStatusHandler(args, context);
break;
case 'approvals':
response = await approvalsHandler(args, context);
break;
case 'log-implementation':
response = await logImplementationHandler(args, context);
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
// Check if the response indicates an error
isError = !response.success;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
response = {
success: false,
message: `Tool execution failed: ${errorMessage}`
};
isError = true;
}
return toMCPResponse(response, isError);
}
```
--------------------------------------------------------------------------------
/.github/workflows/claude-code-review.yml:
--------------------------------------------------------------------------------
```yaml
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"
jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
Please review this pull request and provide feedback on:
- Code quality and best practices
- Potential bugs or issues
- Performance considerations
- Security concerns
- Test coverage
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
```
--------------------------------------------------------------------------------
/.github/workflows/claude.yml:
--------------------------------------------------------------------------------
```yaml
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'
# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
# claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/dashboard_issue.yml:
--------------------------------------------------------------------------------
```yaml
name: 📊 Dashboard/Extension Issue
description: Report an issue with the web dashboard or VSCode extension dashboard
title: "[Dashboard]: "
labels: ["dashboard", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Report issues with the web dashboard or VSCode extension dashboard.
- type: dropdown
id: dashboard-type
attributes:
label: Dashboard Type
description: Which dashboard are you using?
options:
- "Web Dashboard"
- "VSCode Extension Dashboard"
validations:
required: true
- type: dropdown
id: issue-type
attributes:
label: Issue Type
description: What type of dashboard issue is this?
options:
- "Bug - Dashboard not loading"
- "Bug - Display/UI issues"
- "Bug - Updates not showing"
- "Feature request"
- "Performance issue"
- "Other"
validations:
required: true
- type: textarea
id: issue-description
attributes:
label: What's happening?
description: Describe the issue or feature request
placeholder: Tell us what's wrong or what you'd like to see...
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to Reproduce
description: How can we reproduce this issue?
placeholder: |
1. Start dashboard with...
2. Do this...
3. See issue
validations:
required: true
- type: textarea
id: command-used
attributes:
label: Command or Setup Used
description: How did you start the MCP server/dashboard or what VSCode version are you using?
render: shell
placeholder: npx @pimzino/spec-workflow-mcp@latest --dashboard
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Any other details, error messages, or screenshots?
placeholder: Add any other context here...
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/components/ui/card.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from "react";
import { cn } from "@/lib/utils";
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
);
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
);
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
);
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
);
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
);
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
);
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
};
```
--------------------------------------------------------------------------------
/vscode-extension/icons/spec-workflow.svg:
--------------------------------------------------------------------------------
```
<svg xmlns="http://www.w3.org/2000/svg"
width="96" height="96" viewBox="0 0 96 96"
preserveAspectRatio="xMidYMid meet"
fill="none" stroke="currentColor" stroke-width="0.75"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<title>Spec Workflow Icon</title>
<style>*{vector-effect:non-scaling-stroke}</style>
<!-- Background circle -->
<circle cx="48" cy="48" r="48" fill="#ffffff"/>
<!-- Icon copied from activity-bar-icon.svg -->
<!-- Centered 24x24 icon with padding: translate to center, scale, translate back -->
<g transform="translate(48,48) scale(3.5) translate(-12,-12)">
<!-- Page with folded corner -->
<path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z"/>
<path d="M14 2v6h6"/>
<!-- Nodes (precisely aligned) -->
<!-- Start: circle center (9,7.5), r=1.6 -> bottom tangent at y=9.1 -->
<circle cx="9" cy="7.5" r="1.6"/>
<!-- Process: rect from (6,11.5) size 6x3.5; center y=13.25; right mid at (12,13.25) -->
<rect x="6" y="11.5" width="6" height="3.5" rx="0.6"/>
<!-- Decision: diamond centered at (16.25,13.25), touching (16.25,11.5) top,
(18,13.25) right, (16.25,15) bottom, (14.5,13.25) left -->
<polygon points="16.25,11.5 18,13.25 16.25,15 14.5,13.25"/>
<!-- Connectors (end exactly at tangents/edges) -->
<!-- From circle bottom (9,9.1) to process top center (9,11.5) -->
<line x1="9" y1="9.1" x2="9" y2="11.5"/>
<!-- Arrowhead into the process box -->
<polyline points="8.55,11.0 9,11.5 9.45,11.0"/>
<!-- From process right mid (12,13.25) to diamond left point (14.5,13.25) -->
<line x1="12" y1="13.25" x2="14.5" y2="13.25"/>
<!-- Arrowhead into the diamond -->
<polyline points="14.0,12.8 14.5,13.25 14.0,13.7"/>
<!-- Optional: from diamond bottom point (16.25,15) down a bit to imply continuation -->
<line x1="16.25" y1="15" x2="16.25" y2="16.75"/>
<polyline points="15.8,16.25 16.25,16.75 16.7,16.25"/>
</g>
</svg>
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
```typescript
"use client";
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Accordion({
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
}
function AccordionItem({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("border-b last:border-b-0", className)}
{...props}
/>
);
}
function AccordionTrigger({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
);
}
function AccordionContent({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
{...props}
>
<div className={cn("pt-0 pb-4", className)}>{children}</div>
</AccordionPrimitive.Content>
);
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
```
--------------------------------------------------------------------------------
/vscode-extension/tailwind.config.js:
--------------------------------------------------------------------------------
```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./src/webview/**/*.{ts,tsx,js,jsx}',
'./src/webview/index.html',
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [],
}
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "@/lib/utils";
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
);
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
"[body.vscode-light_&]:border [body.vscode-light_&]:border-border/20 [body.vscode-light_&]:shadow-sm",
className
)}
{...props}
/>
);
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
);
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
);
}
export { Tabs, TabsList, TabsTrigger, TabsContent };
```
--------------------------------------------------------------------------------
/scripts/copy-static.cjs:
--------------------------------------------------------------------------------
```
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
function copyDir(src, dest) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDir(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
// Copy markdown directory
const markdownSrc = path.join(__dirname, '..', 'src', 'markdown');
const markdownDest = path.join(__dirname, '..', 'dist', 'markdown');
if (fs.existsSync(markdownSrc)) {
copyDir(markdownSrc, markdownDest);
console.log('✓ Copied markdown files');
}
// Copy locales directory
const localesSrc = path.join(__dirname, '..', 'src', 'locales');
const localesDest = path.join(__dirname, '..', 'dist', 'locales');
if (fs.existsSync(localesSrc)) {
copyDir(localesSrc, localesDest);
console.log('✓ Copied locale files');
}
// Copy icons from old dashboard (we still need these)
const iconsSrc = path.join(__dirname, '..', 'src', 'dashboard', 'public');
const publicDest = path.join(__dirname, '..', 'dist', 'dashboard', 'public');
// Ensure public directory exists
if (!fs.existsSync(publicDest)) {
fs.mkdirSync(publicDest, { recursive: true });
}
// Copy only the icon files from old dashboard
const iconFiles = ['claude-icon.svg', 'claude-icon-dark.svg'];
if (fs.existsSync(iconsSrc)) {
for (const iconFile of iconFiles) {
const srcPath = path.join(iconsSrc, iconFile);
const destPath = path.join(publicDest, iconFile);
if (fs.existsSync(srcPath)) {
fs.copyFileSync(srcPath, destPath);
}
}
console.log('✓ Copied dashboard icon files');
}
// Copy dashboard build as the main dashboard
const newDashSrc = path.join(__dirname, '..', 'src', 'dashboard_frontend', 'dist');
if (fs.existsSync(newDashSrc)) {
// Copy all files from new dashboard to public root
copyDir(newDashSrc, publicDest);
console.log('✓ Copied dashboard as main dashboard');
}
```
--------------------------------------------------------------------------------
/vscode-extension/src/webview/components/ui/button.tsx:
--------------------------------------------------------------------------------
```typescript
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
export { Button, buttonVariants };
```
--------------------------------------------------------------------------------
/src/prompts/inject-spec-workflow-guide.ts:
--------------------------------------------------------------------------------
```typescript
import { Prompt, PromptMessage } from '@modelcontextprotocol/sdk/types.js';
import { PromptDefinition } from './types.js';
import { ToolContext } from '../types.js';
import { specWorkflowGuideHandler } from '../tools/spec-workflow-guide.js';
const prompt: Prompt = {
name: 'inject-spec-workflow-guide',
title: 'Inject Spec Workflow Guide into Context',
description: 'Injects the complete spec-driven development workflow guide into the conversation context. This provides immediate access to all workflow phases, tools, and best practices without requiring separate tool calls.'
};
async function handler(args: Record<string, any>, context: ToolContext): Promise<PromptMessage[]> {
// Call the spec-workflow-guide tool to get the full guide
const toolResponse = await specWorkflowGuideHandler({}, context);
// Extract the guide content from the tool response
const guide = toolResponse.data?.guide || '';
const dashboardUrl = toolResponse.data?.dashboardUrl;
const nextSteps = toolResponse.nextSteps || [];
const messages: PromptMessage[] = [
{
role: 'user',
content: {
type: 'text',
text: `Please review and follow this comprehensive spec-driven development workflow guide:
${guide}
**Current Context:**
- Project: ${context.projectPath}
${dashboardUrl ? `- Dashboard: ${dashboardUrl}` : '- Dashboard: Please start the dashboard or use VS Code extension "Spec Workflow MCP"'}
**Next Steps:**
${nextSteps.map(step => `- ${step}`).join('\n')}
**Important Instructions:**
1. This guide has been injected into your context for immediate reference
2. Follow the workflow sequence exactly: Requirements → Design → Tasks → Implementation
3. Use the MCP tools mentioned in the guide to execute each phase
4. Always request approval between phases using the approvals tool
5. Never proceed to the next phase without successful approval cleanup
Please acknowledge that you've reviewed this workflow guide and are ready to help with spec-driven development.`
}
}
];
return messages;
}
export const injectSpecWorkflowGuidePrompt: PromptDefinition = {
prompt,
handler
};
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
```yaml
name: 🐛 Bug Report
description: Report a bug or issue with the Spec-Driven Workflow MCP Server
title: "[Bug]: "
labels: ["bug", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting a bug! Please provide the essential details below to help us fix the issue quickly.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Describe the bug clearly and concisely
placeholder: Tell us what went wrong...
validations:
required: true
- type: dropdown
id: component
attributes:
label: Component
description: Which component is affected?
options:
- MCP Server
- Web Dashboard
- VSCode Extension
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to Reproduce
description: How can we reproduce this issue?
placeholder: |
1. Run MCP server with '...'
2. Use tool '...'
3. See error
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected Behavior
description: What should have happened instead?
placeholder: Describe what you expected to happen...
validations:
required: true
- type: textarea
id: error-output
attributes:
label: Error Output (if any)
description: Paste any error messages or logs
render: shell
placeholder: Paste error messages here...
- type: input
id: version
attributes:
label: Version
description: What version are you using? (MCP server, dashboard, or extension version)
placeholder: "0.0.19"
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating System
options:
- Windows
- macOS
- Linux
- Other
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Anything else that might help us understand the issue?
placeholder: Add any other details, screenshots, or context here...
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/components/LanguageSelector.tsx:
--------------------------------------------------------------------------------
```typescript
import React from 'react';
import { useTranslation } from 'react-i18next';
interface LanguageSelectorProps {
className?: string;
}
const SUPPORTED_LANGUAGES = [
{ code: 'en', name: 'English', flag: '🇺🇸' },
{ code: 'ja', name: '日本語', flag: '🇯🇵' },
{ code: 'zh', name: '中文', flag: '🇨🇳' },
{ code: 'es', name: 'Español', flag: '🇪🇸' },
{ code: 'pt', name: 'Português', flag: '🇧🇷' },
{ code: 'de', name: 'Deutsch', flag: '🇩🇪' },
{ code: 'fr', name: 'Français', flag: '🇫🇷' },
{ code: 'ru', name: 'Русский', flag: '🇷🇺' },
{ code: 'it', name: 'Italiano', flag: '🇮🇹' },
{ code: 'ko', name: '한국어', flag: '🇰🇷' },
{ code: 'ar', name: 'العربية', flag: '🇸🇦' },
];
export function LanguageSelector({ className = '' }: LanguageSelectorProps) {
const { i18n, t } = useTranslation();
const currentLanguage = SUPPORTED_LANGUAGES.find(lang => lang.code === i18n.language) || SUPPORTED_LANGUAGES[0];
const handleLanguageChange = (languageCode: string) => {
i18n.changeLanguage(languageCode);
// Save preference to localStorage for persistence
localStorage.setItem('preferred-language', languageCode);
};
return (
<div className={`relative ${className}`}>
<select
value={i18n.language}
onChange={(e) => handleLanguageChange(e.target.value)}
className="appearance-none bg-transparent border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-1.5 pr-8 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500"
title={t('language.select')}
aria-label={t('language.select')}
>
{SUPPORTED_LANGUAGES.map((language) => (
<option key={language.code} value={language.code}>
{language.flag} {language.name}
</option>
))}
</select>
{/* Custom dropdown arrow */}
<div className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<svg className="h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
);
}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/i18n-dynamic.ts:
--------------------------------------------------------------------------------
```typescript
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
// Dynamic import function for translations
const loadTranslation = async (lang: string) => {
try {
const translation = await import(`./locales/${lang}.json`);
return translation.default || translation;
} catch (error) {
console.error(`Failed to load translation for ${lang}:`, error);
// Fallback to English if language file not found
if (lang !== 'en') {
const fallback = await import('./locales/en.json');
return fallback.default || fallback;
}
return {};
}
};
// Initialize i18n with dynamic loading
const initI18n = async () => {
// Detect initial language
const detector = new LanguageDetector();
detector.init();
const detectedLang = detector.detect() as string || 'en';
const initialLang = ['en', 'ja', 'zh'].includes(detectedLang) ? detectedLang : 'en';
// Load initial translation
const initialTranslation = await loadTranslation(initialLang);
await i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
[initialLang]: {
translation: initialTranslation,
},
},
fallbackLng: 'en',
detection: {
// Order of detection methods
order: ['localStorage', 'navigator', 'htmlTag'],
// Key to store language preference in localStorage
lookupLocalStorage: 'preferred-language',
// Cache the language detection result in localStorage
caches: ['localStorage'],
},
interpolation: {
escapeValue: true, // Enable for defense-in-depth, even though React provides XSS protection
},
react: {
useSuspense: false, // Disable suspense for async loading
},
});
// Add language changed listener to dynamically load translations
i18n.on('languageChanged', async (lng) => {
if (!i18n.hasResourceBundle(lng, 'translation')) {
const translation = await loadTranslation(lng);
i18n.addResourceBundle(lng, 'translation', translation, true, true);
}
});
return i18n;
};
// Export the initialization promise
export const i18nPromise = initI18n();
// Export i18n instance for backward compatibility
export default i18n;
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/vscode_extension.yml:
--------------------------------------------------------------------------------
```yaml
name: 🧩 VSCode Extension Issue
description: Report an issue specific to the VSCode extension
title: "[Extension]: "
labels: ["extension", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Report issues specific to the VSCode extension functionality.
- type: dropdown
id: issue-type
attributes:
label: Issue Type
description: What type of extension issue is this?
options:
- "Extension not activating"
- "Dashboard not loading in sidebar"
- "Archive feature issue"
- "Sound notifications not working"
- "Approval workflow issue"
- "Project detection issue"
- "Other"
validations:
required: true
- type: textarea
id: issue-description
attributes:
label: What's happening?
description: Describe the issue you're experiencing
placeholder: Tell us what's wrong...
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to Reproduce
description: How can we reproduce this issue?
placeholder: |
1. Open VSCode with project...
2. Click on extension icon...
3. See issue
validations:
required: true
- type: input
id: extension-version
attributes:
label: Extension Version
description: What version of the extension are you using?
placeholder: "0.0.2"
validations:
required: true
- type: input
id: vscode-version
attributes:
label: VSCode Version
description: What version of VSCode are you using? (Help > About)
placeholder: "1.85.0"
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating System
options:
- Windows
- macOS
- Linux
- Other
- type: textarea
id: workspace-info
attributes:
label: Workspace Information
description: Does your project have a .spec-workflow folder?
placeholder: |
- Project has .spec-workflow folder: Yes/No
- Multiple workspaces open: Yes/No
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Any error messages, logs, or screenshots?
placeholder: Add any other context here...
```
--------------------------------------------------------------------------------
/vscode-extension/src/extension/utils/colorUtils.ts:
--------------------------------------------------------------------------------
```typescript
import { HighlightColor } from '../types';
/**
* Convert hex color to RGBA with alpha transparency for background
*/
export function hexToColorObject(hex: string): HighlightColor {
// Validate hex format
if (!isValidHex(hex)) {
// Fallback to default yellow
hex = '#FFEB3B';
}
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return {
bg: `rgba(${r}, ${g}, ${b}, 0.3)`, // 30% transparency for background
border: hex, // Full opacity for border
name: hex.toUpperCase()
};
}
/**
* Validate hex color format
*/
export function isValidHex(hex: string): boolean {
return /^#[0-9A-Fa-f]{6}$/.test(hex);
}
/**
* Get default highlight colors for quick selection
*/
export function getDefaultHighlightColors(): HighlightColor[] {
const defaultColors = [
'#FFEB3B', // Yellow
'#FF5722', // Deep Orange
'#4CAF50', // Green
'#2196F3', // Blue
'#9C27B0', // Purple
'#FF9800', // Orange
'#F44336', // Red
'#00BCD4' // Cyan
];
return defaultColors.map(hex => hexToColorObject(hex));
}
/**
* Generate a random highlight color
*/
export function generateRandomColor(): HighlightColor {
const colors = [
'#FFE082', '#FFAB40', '#FF8A65', '#F8BBD9',
'#CE93D8', '#B39DDB', '#9FA8DA', '#90CAF9',
'#81D4FA', '#80DEEA', '#80CBC4', '#A5D6A7',
'#C5E1A5', '#E6EE9C', '#FFF59D', '#FFCC02'
];
const randomHex = colors[Math.floor(Math.random() * colors.length)];
return hexToColorObject(randomHex);
}
/**
* Ensure color has good contrast for readability
*/
export function ensureReadableColor(hex: string): HighlightColor {
const color = hexToColorObject(hex);
// Calculate luminance to determine if we need to adjust
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
// Formula for relative luminance
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
// If color is too dark, increase background alpha for better visibility
if (luminance < 0.3) {
const newR = Math.min(255, r + 50);
const newG = Math.min(255, g + 50);
const newB = Math.min(255, b + 50);
return {
bg: `rgba(${newR}, ${newG}, ${newB}, 0.4)`,
border: hex,
name: hex.toUpperCase()
};
}
return color;
}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/components/I18nErrorBoundary.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { Component, ReactNode } from 'react';
interface Props {
children: ReactNode;
}
interface State {
hasI18nError: boolean;
error?: Error;
}
/**
* Error boundary to handle i18n initialization failures gracefully.
* Falls back to English text when translation system fails.
*/
export class I18nErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasI18nError: false };
}
static getDerivedStateFromError(error: Error): State {
// Check if this is an i18n-related error
const isI18nError = error.message.includes('i18n') ||
error.message.includes('translation') ||
error.message.includes('react-i18next');
return {
hasI18nError: isI18nError,
error: isI18nError ? error : undefined
};
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// Log i18n errors for debugging
if (this.state.hasI18nError) {
console.error('I18n Error Boundary caught an error:', error, errorInfo);
}
}
render() {
if (this.state.hasI18nError) {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100 flex items-center justify-center">
<div className="text-center p-8">
<div className="text-6xl mb-4">⚠️</div>
<h1 className="text-2xl font-bold mb-4">Translation System Error</h1>
<p className="text-gray-600 dark:text-gray-300 mb-4">
The translation system failed to initialize. The application is running with fallback text.
</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
>
Reload Application
</button>
{process.env.NODE_ENV === 'development' && (
<details className="mt-4 text-left">
<summary className="cursor-pointer text-sm text-gray-500">
Error Details (Development)
</summary>
<pre className="mt-2 p-2 bg-gray-100 dark:bg-gray-800 rounded text-xs overflow-auto">
{this.state.error?.stack}
</pre>
</details>
)}
</div>
</div>
);
}
return this.props.children;
}
}
```
--------------------------------------------------------------------------------
/src/prompts/inject-steering-guide.ts:
--------------------------------------------------------------------------------
```typescript
import { Prompt, PromptMessage } from '@modelcontextprotocol/sdk/types.js';
import { PromptDefinition } from './types.js';
import { ToolContext } from '../types.js';
import { steeringGuideHandler } from '../tools/steering-guide.js';
const prompt: Prompt = {
name: 'inject-steering-guide',
title: 'Inject Steering Guide into Context',
description: 'Injects the complete steering document workflow guide into the conversation context. This provides instructions for creating project-level guidance documents (product.md, tech.md, structure.md) when explicitly requested by the user.'
};
async function handler(args: Record<string, any>, context: ToolContext): Promise<PromptMessage[]> {
// Call the steering-guide tool to get the full guide
const toolResponse = await steeringGuideHandler({}, context);
// Extract the guide content from the tool response
const guide = toolResponse.data?.guide || '';
const dashboardUrl = toolResponse.data?.dashboardUrl;
const nextSteps = toolResponse.nextSteps || [];
const messages: PromptMessage[] = [
{
role: 'user',
content: {
type: 'text',
text: `Please review and follow this steering document workflow guide:
${guide}
**Current Context:**
- Project: ${context.projectPath}
${dashboardUrl ? `- Dashboard: ${dashboardUrl}` : '- Dashboard: Please start the dashboard or use VS Code extension "Spec Workflow MCP"'}
**Next Steps:**
${nextSteps.map(step => `- ${step}`).join('\n')}
**Important Instructions:**
1. This guide has been injected into your context for creating steering documents
2. Only proceed if the user explicitly requested steering document creation
3. Follow the sequence exactly: product.md → tech.md → structure.md
4. Read templates from .spec-workflow/templates/ directory
5. Create documents in .spec-workflow/steering/ directory
6. Request approval after each document using the approvals tool
7. Never proceed to the next document without successful approval cleanup
**Note:** Steering documents are NOT part of the standard spec workflow. They are project-level guidance documents that should only be created when explicitly requested by the user. These documents establish vision, architecture, and conventions for established codebases.
Please acknowledge that you've reviewed this steering workflow guide and confirm whether the user wants to create steering documents.`
}
}
];
return messages;
}
export const injectSteeringGuidePrompt: PromptDefinition = {
prompt,
handler
};
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@pimzino/spec-workflow-mcp",
"version": "2.0.7",
"description": "MCP server for spec-driven development workflow with real-time web dashboard",
"main": "dist/index.js",
"type": "module",
"bin": {
"spec-workflow-mcp": "dist/index.js"
},
"files": [
"dist/**/*",
"README.md",
"CHANGELOG.md",
"LICENSE"
],
"scripts": {
"build": "npm run validate:i18n && npm run clean && tsc && npm run build:dashboard && npm run copy-static",
"copy-static": "node scripts/copy-static.cjs",
"dev": "tsx src/index.ts",
"start": "node dist/index.js",
"dev:dashboard": "vite --config src/dashboard_frontend/vite.config.ts",
"build:dashboard": "vite build --config src/dashboard_frontend/vite.config.ts",
"clean": "rimraf dist",
"validate:i18n": "node scripts/validate-i18n.js",
"test": "vitest",
"test:watch": "vitest --watch",
"test:coverage": "vitest --coverage",
"prepublishOnly": "npm run build"
},
"keywords": [
"mcp",
"model-context-protocol",
"spec-workflow",
"ai-development",
"claude",
"cursor",
"development-workflow",
"project-management"
],
"author": "",
"license": "GPL-3.0",
"dependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fastify/static": "^7.0.4",
"@fastify/websocket": "^8.2.1",
"@heroicons/react": "^2.2.0",
"@modelcontextprotocol/sdk": "^0.5.0",
"@tailwindcss/vite": "^4.1.13",
"@toon-format/toon": "^0.8.0",
"chokidar": "^3.5.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"diff": "^5.1.0",
"fastify": "^4.24.3",
"highlight.js": "^11.9.0",
"howler": "^2.2.4",
"i18next": "^25.5.2",
"i18next-browser-languagedetector": "^8.2.0",
"markdown-it": "^14.1.0",
"mermaid": "^10.9.1",
"node-cron": "^4.2.1",
"open": "^8.4.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^15.7.3",
"react-router-dom": "^6.26.2",
"simple-git": "^3.28.0",
"tailwind-merge": "^3.3.1",
"toml": "^3.0.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.16",
"@types/diff": "^5.0.3",
"@types/node": "^22.18.1",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/ws": "^8.5.13",
"@vitejs/plugin-react": "^4.3.3",
"@vitest/coverage-v8": "^2.1.8",
"rimraf": "^6.0.1",
"tailwindcss": "^4.1.13",
"tsx": "^4.7.0",
"typescript": "^5.7.2",
"vite": "^5.4.8",
"vitest": "^2.1.8"
}
}
```
--------------------------------------------------------------------------------
/src/markdown/templates/design-template.md:
--------------------------------------------------------------------------------
```markdown
# Design Document
## Overview
[High-level description of the feature and its place in the overall system]
## Steering Document Alignment
### Technical Standards (tech.md)
[How the design follows documented technical patterns and standards]
### Project Structure (structure.md)
[How the implementation will follow project organization conventions]
## Code Reuse Analysis
[What existing code will be leveraged, extended, or integrated with this feature]
### Existing Components to Leverage
- **[Component/Utility Name]**: [How it will be used]
- **[Service/Helper Name]**: [How it will be extended]
### Integration Points
- **[Existing System/API]**: [How the new feature will integrate]
- **[Database/Storage]**: [How data will connect to existing schemas]
## Architecture
[Describe the overall architecture and design patterns used]
### Modular Design Principles
- **Single File Responsibility**: Each file should handle one specific concern or domain
- **Component Isolation**: Create small, focused components rather than large monolithic files
- **Service Layer Separation**: Separate data access, business logic, and presentation layers
- **Utility Modularity**: Break utilities into focused, single-purpose modules
```mermaid
graph TD
A[Component A] --> B[Component B]
B --> C[Component C]
```
## Components and Interfaces
### Component 1
- **Purpose:** [What this component does]
- **Interfaces:** [Public methods/APIs]
- **Dependencies:** [What it depends on]
- **Reuses:** [Existing components/utilities it builds upon]
### Component 2
- **Purpose:** [What this component does]
- **Interfaces:** [Public methods/APIs]
- **Dependencies:** [What it depends on]
- **Reuses:** [Existing components/utilities it builds upon]
## Data Models
### Model 1
```
[Define the structure of Model1 in your language]
- id: [unique identifier type]
- name: [string/text type]
- [Additional properties as needed]
```
### Model 2
```
[Define the structure of Model2 in your language]
- id: [unique identifier type]
- [Additional properties as needed]
```
## Error Handling
### Error Scenarios
1. **Scenario 1:** [Description]
- **Handling:** [How to handle]
- **User Impact:** [What user sees]
2. **Scenario 2:** [Description]
- **Handling:** [How to handle]
- **User Impact:** [What user sees]
## Testing Strategy
### Unit Testing
- [Unit testing approach]
- [Key components to test]
### Integration Testing
- [Integration testing approach]
- [Key flows to test]
### End-to-End Testing
- [E2E testing approach]
- [User scenarios to test]
```
--------------------------------------------------------------------------------
/src/prompts/create-steering-doc.ts:
--------------------------------------------------------------------------------
```typescript
import { Prompt, PromptMessage } from '@modelcontextprotocol/sdk/types.js';
import { PromptDefinition } from './types.js';
import { ToolContext } from '../types.js';
const prompt: Prompt = {
name: 'create-steering-doc',
title: 'Create Steering Document',
description: 'Guide for creating project steering documents (product, tech, structure) directly in the file system. These provide high-level project guidance.',
arguments: [
{
name: 'docType',
description: 'Type of steering document: product, tech, or structure',
required: true
},
{
name: 'scope',
description: 'Scope of the steering document (e.g., frontend, backend, full-stack)',
required: false
}
]
};
async function handler(args: Record<string, any>, context: ToolContext): Promise<PromptMessage[]> {
const { docType, scope } = args;
if (!docType) {
throw new Error('docType is a required argument');
}
const validDocTypes = ['product', 'tech', 'structure'];
if (!validDocTypes.includes(docType)) {
throw new Error(`docType must be one of: ${validDocTypes.join(', ')}`);
}
const messages: PromptMessage[] = [
{
role: 'user',
content: {
type: 'text',
text: `Create a ${docType} steering document for the project.
**Context:**
- Project: ${context.projectPath}
- Steering document type: ${docType}
${scope ? `- Scope: ${scope}` : ''}
${context.dashboardUrl ? `- Dashboard: ${context.dashboardUrl}` : ''}
**Instructions:**
1. First, read the template at: .spec-workflow/templates/${docType}-template.md
2. Check if steering docs exist at: .spec-workflow/steering/
3. Create comprehensive content following the template structure
4. Create the document at: .spec-workflow/steering/${docType}.md
5. After creating, use approvals tool with action:'request' to get user approval
**File Paths:**
- Template location: .spec-workflow/templates/${docType}-template.md
- Document destination: .spec-workflow/steering/${docType}.md
**Steering Document Types:**
- **product**: Defines project vision, goals, and user outcomes
- **tech**: Documents technology decisions and architecture patterns
- **structure**: Maps codebase organization and conventions
**Key Principles:**
- Be specific and actionable
- Include examples where helpful
- Consider both technical and business requirements
- Provide clear guidance for future development
- Templates are automatically updated on server start
Please read the ${docType} template and create a comprehensive steering document at the specified path.`
}
}
];
return messages;
}
export const createSteeringDocPrompt: PromptDefinition = {
prompt,
handler
};
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/modules/notifications/VolumeControl.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { useState } from 'react';
import { useNotifications, useNotificationState } from './NotificationProvider';
import { useTranslation } from 'react-i18next';
import styles from './VolumeControl.module.css';
export function VolumeControl() {
const { toggleSound, setVolume } = useNotifications();
const { soundEnabled, volume } = useNotificationState();
const { t } = useTranslation();
const [showSlider, setShowSlider] = useState(false);
// Convert volume (0.0-1.0) to percentage (0-100) for display
const volumePercentage = Math.round(volume * 100);
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const percentage = parseInt(e.target.value);
const volumeValue = percentage / 100;
setVolume(volumeValue);
};
return (
<div className="flex items-center gap-2">
{/* Mute/Unmute Button */}
<button
onClick={toggleSound}
className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
title={soundEnabled ? t('volumeControl.mute') : t('volumeControl.enable')}
>
{soundEnabled ? (
// Volume on icon
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 14.142M8 19a1 1 0 01-1-1v-6a1 1 0 011-1h2.172a3 3 0 001.414-.586L15 7a1 1 0 011 1v8a1 1 0 01-1 1l-3.414-3.414A3 3 0 0010.172 13H8a1 1 0 01-1-1V7a1 1 0 011-1z" />
</svg>
) : (
// Volume off icon
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5.586 15H4a1 1 0 01-1-1V9a1 1 0 011-1h1.586l4.707-4.707C10.923 2.663 12 3.109 12 4v16c0 .891-1.077 1.337-1.707.707L5.586 16z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2" />
</svg>
)}
</button>
{/* Volume Slider - only show when sound is enabled */}
{soundEnabled && (
<div className="flex items-center gap-2">
<input
type="range"
min="0"
max="100"
value={volumePercentage}
onChange={handleVolumeChange}
className={`${styles.slider} ${styles.dark}`}
title={t('volumeControl.volumeLevel', { percentage: volumePercentage })}
/>
<span className="text-xs text-gray-500 dark:text-gray-400 min-w-[2rem]">
{volumePercentage}%
</span>
</div>
)}
</div>
);
}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/i18n.ts:
--------------------------------------------------------------------------------
```typescript
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
/**
* Dynamic Import Configuration for i18n
*
* When to use dynamic imports:
* - Large applications with many language files (>100KB per language)
* - Applications where users typically use only one language
* - When initial load time is critical
*
* Trade-offs:
* - Pros: Smaller initial bundle size, faster initial page load
* - Cons: Slight delay when switching languages for the first time
*
* To enable dynamic imports:
* 1. Set VITE_I18N_DYNAMIC=true in your .env file
* 2. Import from './i18n-dynamic' instead of './i18n' in main.tsx
*
* Example:
* ```typescript
* // main.tsx
* import './i18n-dynamic'; // Use this for dynamic loading
* // import './i18n'; // Use this for static loading (default)
* ```
*/
// Static imports (default for backward compatibility)
import enTranslation from './locales/en.json';
import jaTranslation from './locales/ja.json';
import zhTranslation from './locales/zh.json';
import esTranslation from './locales/es.json';
import ptTranslation from './locales/pt.json';
import deTranslation from './locales/de.json';
import frTranslation from './locales/fr.json';
import ruTranslation from './locales/ru.json';
import itTranslation from './locales/it.json';
import koTranslation from './locales/ko.json';
import arTranslation from './locales/ar.json';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: {
translation: enTranslation,
},
ja: {
translation: jaTranslation,
},
zh: {
translation: zhTranslation,
},
es: {
translation: esTranslation,
},
pt: {
translation: ptTranslation,
},
de: {
translation: deTranslation,
},
fr: {
translation: frTranslation,
},
ru: {
translation: ruTranslation,
},
it: {
translation: itTranslation,
},
ko: {
translation: koTranslation,
},
ar: {
translation: arTranslation,
},
},
fallbackLng: 'en',
detection: {
// Order of detection methods
order: ['localStorage', 'navigator', 'htmlTag'],
// Key to store language preference in localStorage
lookupLocalStorage: 'preferred-language',
// Cache the language detection result in localStorage
caches: ['localStorage'],
},
interpolation: {
escapeValue: true, // Enable for defense-in-depth, even though React provides XSS protection
},
react: {
useSuspense: false, // Disable suspense to use static imports synchronously
},
});
export default i18n;
```
--------------------------------------------------------------------------------
/vscode-extension/vite.config.ts:
--------------------------------------------------------------------------------
```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import fs from 'fs'
// Plugin to copy sound assets and locale files
function copyAssetsPlugin() {
return {
name: 'copy-assets',
writeBundle() {
// Copy sound files
const soundSourceDir = path.resolve(__dirname, 'webview-assets/sounds')
const soundTargetDir = path.resolve(__dirname, 'webview-dist/sounds')
// Create target directory if it doesn't exist
if (!fs.existsSync(soundTargetDir)) {
fs.mkdirSync(soundTargetDir, { recursive: true })
}
// Copy sound files
if (fs.existsSync(soundSourceDir)) {
const files = fs.readdirSync(soundSourceDir)
files.forEach(file => {
const sourcePath = path.join(soundSourceDir, file)
const targetPath = path.join(soundTargetDir, file)
fs.copyFileSync(sourcePath, targetPath)
console.log(`Copied sound asset: ${file}`)
})
}
// Copy locale files
const localesSourceDir = path.resolve(__dirname, 'src/webview/locales')
const localesTargetDir = path.resolve(__dirname, 'webview-dist/locales')
// Create target directory if it doesn't exist
if (!fs.existsSync(localesTargetDir)) {
fs.mkdirSync(localesTargetDir, { recursive: true })
}
// Copy locale files
if (fs.existsSync(localesSourceDir)) {
const files = fs.readdirSync(localesSourceDir)
files.forEach(file => {
if (file.endsWith('.json')) {
const sourcePath = path.join(localesSourceDir, file)
const targetPath = path.join(localesTargetDir, file)
fs.copyFileSync(sourcePath, targetPath)
console.log(`Copied locale file: ${file}`)
}
})
}
}
}
}
// Dynamically import Tailwind CSS v4 plugin
async function createConfig() {
const { default: tailwindcss } = await import('@tailwindcss/vite')
return {
plugins: [
react(),
tailwindcss(),
copyAssetsPlugin(),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src/webview"),
},
},
build: {
outDir: path.resolve(__dirname, 'webview-dist'),
rollupOptions: {
input: {
main: 'src/webview/index.html',
'comment-modal': 'src/webview/comment-modal.html'
},
output: {
entryFileNames: '[name].js',
chunkFileNames: '[name].js',
assetFileNames: (assetInfo: any) => {
// Force CSS files to be named globals.css
if (assetInfo.name?.endsWith('.css')) {
return 'globals.css';
}
return '[name].[ext]';
}
}
},
minify: 'esbuild' as const,
target: 'es2020'
},
server: {
port: 5173,
strictPort: true
},
root: 'src/webview'
}
}
// https://vite.dev/config/
export default defineConfig(createConfig())
```
--------------------------------------------------------------------------------
/vscode-extension/src/test/extension.test.ts:
--------------------------------------------------------------------------------
```typescript
import * as assert from 'assert';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
import { ApprovalEditorService } from '../extension/services/ApprovalEditorService';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
suite('Path Resolution Tests', () => {
let tempDir: string;
let approvalEditorService: ApprovalEditorService;
suiteSetup(async () => {
// Create a temporary directory structure for testing
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'spec-workflow-test-'));
// Create test directory structure
const specWorkflowDir = path.join(tempDir, '.spec-workflow');
const specsDir = path.join(specWorkflowDir, 'specs');
const testDir = path.join(specWorkflowDir, 'test');
fs.mkdirSync(specWorkflowDir, { recursive: true });
fs.mkdirSync(specsDir, { recursive: true });
fs.mkdirSync(testDir, { recursive: true });
// Create test files
fs.writeFileSync(path.join(specsDir, 'tasks.md'), '# Test Tasks');
fs.writeFileSync(path.join(testDir, 'tasks.md'), '# Test Tasks in Test Dir');
fs.writeFileSync(path.join(tempDir, 'root-tasks.md'), '# Root Tasks');
approvalEditorService = new ApprovalEditorService();
});
suiteTeardown(() => {
// Clean up temporary directory
if (tempDir && fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
test('should resolve paths with forward slashes', async () => {
const testPath = '.spec-workflow/specs/tasks.md';
const result = await (approvalEditorService as any).resolveApprovalFilePath(testPath, tempDir);
assert.ok(result, 'Should resolve path with forward slashes');
assert.ok(result.includes('specs'), 'Should resolve to specs directory');
});
test('should resolve paths with backslashes', async () => {
const testPath = '.spec-workflow\\test\\tasks.md';
const result = await (approvalEditorService as any).resolveApprovalFilePath(testPath, tempDir);
assert.ok(result, 'Should resolve path with backslashes');
assert.ok(result.includes('test'), 'Should resolve to test directory');
});
test('should resolve relative paths', async () => {
const testPath = 'root-tasks.md';
const result = await (approvalEditorService as any).resolveApprovalFilePath(testPath, tempDir);
assert.ok(result, 'Should resolve relative path');
assert.ok(result.includes('root-tasks.md'), 'Should resolve to root file');
});
test('should handle missing files gracefully', async () => {
const testPath = '.spec-workflow/nonexistent/file.md';
const result = await (approvalEditorService as any).resolveApprovalFilePath(testPath, tempDir);
assert.strictEqual(result, null, 'Should return null for nonexistent files');
});
});
});
```
--------------------------------------------------------------------------------
/src/prompts/spec-status.ts:
--------------------------------------------------------------------------------
```typescript
import { Prompt, PromptMessage } from '@modelcontextprotocol/sdk/types.js';
import { PromptDefinition } from './types.js';
import { ToolContext } from '../types.js';
const prompt: Prompt = {
name: 'spec-status',
title: 'Specification Status Overview',
description: 'Get comprehensive status overview of specification documents, tasks, and approval workflows. Useful for project tracking and progress reporting.',
arguments: [
{
name: 'specName',
description: 'Feature name in kebab-case to get status for (optional - if not provided, shows all specs)',
required: false
},
{
name: 'detailed',
description: 'Show detailed status including task breakdown and approval history',
required: false
}
]
};
async function handler(args: Record<string, any>, context: ToolContext): Promise<PromptMessage[]> {
const { specName, detailed } = args;
const scope = specName ? `the "${specName}" feature` : 'all specifications in the project';
const detailLevel = detailed ? 'detailed' : 'summary';
const messages: PromptMessage[] = [
{
role: 'user',
content: {
type: 'text',
text: `Get ${detailLevel} status overview for ${scope}.
**Context:**
- Project: ${context.projectPath}
${specName ? `- Feature: ${specName}` : '- Scope: All specifications'}
- Detail level: ${detailLevel}
${context.dashboardUrl ? `- Dashboard: ${context.dashboardUrl}` : ''}
**Instructions:**
${specName ?
`1. Use the spec-status tool with specName "${specName}" to get status information
2. If you need detailed task information, read the tasks.md file directly at .spec-workflow/specs/${specName}/tasks.md
3. Check for any pending approvals using approvals tool with action:'status'` :
`1. List directory .spec-workflow/specs/ to see all specifications
2. Use the spec-status tool to get status for each specification
3. Provide a consolidated overview of project progress`}
**Status Information Includes:**
- **Document Status**: Which documents exist (requirements, design, tasks)
- **Task Progress**: Completion status and remaining work
- **Approval Status**: Pending, approved, or rejected approvals
- **File Information**: Last modified dates and file sizes
- **Workflow Stage**: Current phase in the spec-driven development process
**Workflow Stages:**
1. **Planning**: Requirements document created and approved
2. **Design**: Design document created and approved
3. **Implementation**: Tasks defined and implementation in progress
4. **Review**: Implementation complete, awaiting final approval
5. **Complete**: All tasks complete and approved
${detailed ? `**Detailed Information Includes:**
- Individual task breakdown with completion status
- Approval request history and reviewer comments
- File modification timestamps
- Steering document references
- Dependency tracking between specs` : ''}
Please provide a comprehensive status report that helps understand the current state and next steps.`
}
}
];
return messages;
}
export const specStatusPrompt: PromptDefinition = {
prompt,
handler
};
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/modules/modals/ConfirmationModal.tsx:
--------------------------------------------------------------------------------
```typescript
import React from 'react';
import { useTranslation } from 'react-i18next';
interface ConfirmationModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
title: string;
message: string;
confirmText?: string;
cancelText?: string;
variant?: 'default' | 'danger';
}
export function ConfirmationModal({
isOpen,
onClose,
onConfirm,
title,
message,
confirmText = 'Confirm',
cancelText = 'Cancel',
variant = 'default'
}: ConfirmationModalProps) {
const { t } = useTranslation();
const handleConfirm = () => {
onConfirm();
onClose();
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
if (e.key === 'Enter') {
e.preventDefault();
handleConfirm();
}
};
if (!isOpen) return null;
const confirmButtonClasses = variant === 'danger'
? 'px-4 py-2 text-sm font-medium text-white bg-red-600 border border-transparent rounded-md hover:bg-red-700 focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition-colors'
: 'px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors';
return (
<div
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
onKeyDown={handleKeyDown}
tabIndex={-1}
>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md">
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{title}
</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
aria-label={t('common.closeModalAria')}
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Content */}
<div className="p-6">
<p className="text-gray-700 dark:text-gray-300">
{message}
</p>
</div>
{/* Footer */}
<div className="flex items-center justify-end gap-3 p-6 border-t border-gray-200 dark:border-gray-700">
<button
onClick={onClose}
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
autoFocus
>
{cancelText}
</button>
<button
onClick={handleConfirm}
className={confirmButtonClasses}
>
{confirmText}
</button>
</div>
</div>
</div>
);
}
```
--------------------------------------------------------------------------------
/src/dashboard_frontend/src/modules/pages/JobTemplates.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Predefined job templates for common automation scenarios
*/
export interface JobTemplate {
name: string;
type: 'cleanup-approvals' | 'cleanup-specs' | 'cleanup-archived-specs';
daysOld: number;
schedule: string;
description: string;
}
export const JOB_TEMPLATES: Record<string, JobTemplate> = {
// Approval cleanup templates
'cleanup-approvals-monthly': {
name: 'Monthly Approval Cleanup',
type: 'cleanup-approvals',
daysOld: 90,
schedule: '0 2 1 * *', // First day of month at 2 AM
description: 'Delete approval records older than 90 days, runs monthly'
},
'cleanup-approvals-weekly': {
name: 'Weekly Approval Cleanup',
type: 'cleanup-approvals',
daysOld: 30,
schedule: '0 2 ? * SUN', // Every Sunday at 2 AM
description: 'Delete approval records older than 30 days, runs weekly'
},
'cleanup-approvals-daily': {
name: 'Daily Approval Cleanup',
type: 'cleanup-approvals',
daysOld: 7,
schedule: '0 2 * * *', // Every day at 2 AM
description: 'Delete approval records older than 7 days, runs daily'
},
// Spec cleanup templates
'cleanup-specs-quarterly': {
name: 'Quarterly Spec Cleanup',
type: 'cleanup-specs',
daysOld: 180,
schedule: '0 3 1 1,4,7,10 *', // First day of Q1, Q2, Q3, Q4 at 3 AM
description: 'Delete specs older than 180 days, runs quarterly'
},
'cleanup-specs-monthly': {
name: 'Monthly Spec Cleanup',
type: 'cleanup-specs',
daysOld: 120,
schedule: '0 3 1 * *', // First day of month at 3 AM
description: 'Delete specs older than 120 days, runs monthly'
},
'cleanup-specs-weekly': {
name: 'Weekly Spec Cleanup',
type: 'cleanup-specs',
daysOld: 60,
schedule: '0 3 ? * SUN', // Every Sunday at 3 AM
description: 'Delete specs older than 60 days, runs weekly'
},
// Archived specs cleanup templates
'cleanup-archived-specs-semi-annual': {
name: 'Semi-Annual Archived Spec Cleanup',
type: 'cleanup-archived-specs',
daysOld: 365,
schedule: '0 4 1 1,7 *', // January 1st and July 1st at 4 AM
description: 'Delete archived specs older than 1 year, runs twice yearly'
},
'cleanup-archived-specs-annual': {
name: 'Annual Archived Spec Cleanup',
type: 'cleanup-archived-specs',
daysOld: 730,
schedule: '0 4 1 1 *', // January 1st at 4 AM
description: 'Delete archived specs older than 2 years, runs annually'
},
'cleanup-archived-specs-monthly': {
name: 'Monthly Archived Spec Cleanup',
type: 'cleanup-archived-specs',
daysOld: 180,
schedule: '0 4 1 * *', // First day of month at 4 AM
description: 'Delete archived specs older than 180 days, runs monthly'
}
};
/**
* Get templates by job type
*/
export function getTemplatesByType(
type: 'cleanup-approvals' | 'cleanup-specs' | 'cleanup-archived-specs'
): JobTemplate[] {
return Object.values(JOB_TEMPLATES).filter(template => template.type === type);
}
/**
* Get a template by key
*/
export function getTemplate(key: string): JobTemplate | undefined {
return JOB_TEMPLATES[key];
}
/**
* Get all available templates
*/
export function getAllTemplates(): JobTemplate[] {
return Object.values(JOB_TEMPLATES);
}
```