#
tokens: 46417/50000 33/34 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/kingkongshot/specs-workflow-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── .npmignore
├── api
│   └── spec-workflow.openapi.yaml
├── eslint.config.js
├── LICENSE
├── logo.png
├── package-lock.json
├── package.json
├── README-zh.md
├── README.md
├── scripts
│   ├── generateOpenApiWebUI.ts
│   ├── generateTypes.ts
│   ├── publish-npm.sh
│   ├── sync-package.sh
│   └── validateOpenApi.ts
├── src
│   ├── features
│   │   ├── check
│   │   │   ├── analyzeStage.ts
│   │   │   ├── checkWorkflow.ts
│   │   │   └── generateNextDocument.ts
│   │   ├── confirm
│   │   │   └── confirmStage.ts
│   │   ├── executeWorkflow.ts
│   │   ├── init
│   │   │   ├── createRequirementsDoc.ts
│   │   │   └── initWorkflow.ts
│   │   ├── shared
│   │   │   ├── confirmationStatus.ts
│   │   │   ├── documentAnalyzer.ts
│   │   │   ├── documentStatus.ts
│   │   │   ├── documentTemplates.ts
│   │   │   ├── documentUtils.ts
│   │   │   ├── mcpTypes.ts
│   │   │   ├── openApiLoader.ts
│   │   │   ├── openApiTypes.ts
│   │   │   ├── progressCalculator.ts
│   │   │   ├── responseBuilder.ts
│   │   │   ├── taskGuidanceTemplate.ts
│   │   │   ├── taskParser.ts
│   │   │   └── typeGuards.ts
│   │   ├── skip
│   │   │   └── skipStage.ts
│   │   └── task
│   │       └── completeTask.ts
│   ├── index.ts
│   └── tools
│       └── specWorkflowTool.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Claude configuration
 2 | .claude
 3 | /memory
 4 | 
 5 | # Dependencies
 6 | node_modules/
 7 | npm-debug.log*
 8 | yarn-debug.log*
 9 | yarn-error.log*
10 | 
11 | # Testing related
12 | # 排除所有测试相关文件
13 | /src/**/*.test.js
14 | /src/**/*.spec.js
15 | /src/**/*.test.ts
16 | /src/**/*.spec.ts
17 | coverage/
18 | # test-specs/ - 在 dev 分支中保留测试套件
19 | test-specs/reports/
20 | test-specs/node_modules/
21 | test-specs/temp/
22 | 
23 | # Documentation
24 | docs/
25 | guidance/zh/
26 | 
27 | # Build artifacts
28 | dist/
29 | build/
30 | *.log
31 | 
32 | # Generated package directory (auto-generated during build)
33 | package/
34 | 
35 | # Environment configuration
36 | .env
37 | .env.local
38 | .env.*.local
39 | test-specs/.env.test
40 | 
41 | # Editor configuration
42 | .vscode/
43 | .idea/
44 | *.swp
45 | *.swo
46 | *~
47 | 
48 | # System files
49 | .DS_Store
50 | Thumbs.db
51 | 
52 | # Temporary files
53 | *.tmp
54 | *.temp
55 | .cache/
56 | 
57 | # Local configuration
58 | *.local.*
59 | mcp-config.json
60 | CLAUDE.local.md
61 | 
62 | # Preview files
63 | preview/
64 | 
65 | # Prompt files (migrated to OpenAPI)
66 | prompts/
67 | 
68 | # WebUI generated files
69 | webui/
70 | /specs
71 | 
72 | # Debug files
73 | debug.sh
74 | 
75 | # Security - Never commit these files
76 | push-to-github.sh
77 | *-token.sh
78 | *.token
79 | .clinerules
80 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Spec Workflow MCP
  2 | 
  3 | [![npm version](https://img.shields.io/npm/v/spec-workflow-mcp.svg)](https://www.npmjs.com/package/spec-workflow-mcp)
  4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
  5 | [![MCP](https://img.shields.io/badge/MCP-Compatible-blue)](https://modelcontextprotocol.com)
  6 | 
  7 | [English](README.md) | [简体中文](README-zh.md)
  8 | 
  9 | Guide AI to systematically complete software development through a structured **Requirements → Design → Tasks** workflow, ensuring code implementation stays aligned with business needs.
 10 | 
 11 | ## Why Use It?
 12 | 
 13 | ### ❌ Without Spec Workflow
 14 | - AI jumps randomly between tasks, lacking systematic approach
 15 | - Requirements disconnect from actual code implementation
 16 | - Scattered documentation, difficult to track project progress
 17 | - Missing design decision records
 18 | 
 19 | ### ✅ With Spec Workflow
 20 | - AI completes tasks sequentially, maintaining focus and context
 21 | - Complete traceability from user stories to code implementation
 22 | - Standardized document templates with automatic progress management
 23 | - Each stage requires confirmation, ensuring correct direction
 24 | - **Persistent progress**: Continue from where you left off with `check`, even in new conversations
 25 | 
 26 | ## Recent Updates
 27 | 
 28 | > **v1.0.7**
 29 | > - 🎯 Improved reliability for most models to manage tasks with spec workflow
 30 | >
 31 | > **v1.0.6**
 32 | > - ✨ Batch task completion: Complete multiple tasks at once for faster progress on large projects
 33 | > 
 34 | > **v1.0.5** 
 35 | > - 🐛 Edge case fixes: Distinguish between "task not found" and "task already completed" to prevent workflow interruption
 36 | > 
 37 | > **v1.0.4**
 38 | > - ✅ Task management: Added task completion tracking for systematic project progression
 39 | > 
 40 | > **v1.0.3**
 41 | > - 🎉 Initial release: Core workflow framework for Requirements → Design → Tasks
 42 | 
 43 | ## Quick Start
 44 | 
 45 | ### 1. Install (Claude Code Example)
 46 | ```bash
 47 | claude mcp add spec-workflow-mcp -s user -- npx -y spec-workflow-mcp@latest
 48 | ```
 49 | 
 50 | See [full installation guide](#installation) for other clients.
 51 | 
 52 | ### 2. Start a New Project
 53 | ```
 54 | "Help me use spec workflow to create a user authentication system"
 55 | ```
 56 | 
 57 | ### 3. Continue Existing Project
 58 | ```
 59 | "Use spec workflow to check ./my-project"
 60 | ```
 61 | 
 62 | The AI will automatically detect project status and continue from where it left off.
 63 | 
 64 | ## Workflow Example
 65 | 
 66 | ### 1. You describe requirements
 67 | ```
 68 | You: "I need to build a user authentication system"
 69 | ```
 70 | 
 71 | ### 2. AI creates structured documents
 72 | ```
 73 | AI: "I'll help you create spec workflow for user authentication..."
 74 | 
 75 | 📝 requirements.md - User stories and functional requirements
 76 | 🎨 design.md - Technical architecture and design decisions
 77 | ✅ tasks.md - Concrete implementation task list
 78 | ```
 79 | 
 80 | ### 3. Review and implement step by step
 81 | After each stage, the AI requests your confirmation before proceeding, ensuring the project stays on the right track.
 82 | 
 83 | ## Document Organization
 84 | 
 85 | ### Basic Structure
 86 | ```
 87 | my-project/specs/
 88 | ├── requirements.md              # Requirements: user stories, functional specs
 89 | ├── design.md                    # Design: architecture, APIs, data models
 90 | ├── tasks.md                     # Tasks: numbered implementation steps
 91 | └── .workflow-confirmations.json # Status: automatic progress tracking
 92 | ```
 93 | 
 94 | ### Multi-module Projects
 95 | ```
 96 | my-project/specs/
 97 | ├── user-authentication/         # Auth module
 98 | ├── payment-system/             # Payment module
 99 | └── notification-service/       # Notification module
100 | ```
101 | 
102 | You can specify any directory: `"Use spec workflow to create auth docs in ./src/features/auth"`
103 | 
104 | ## AI Usage Guide
105 | 
106 | ### 🤖 Make AI Use This Tool Better
107 | 
108 | **Strongly recommended** to add the following prompt to your AI assistant configuration. Without it, AI may:
109 | - ❌ Not know when to invoke Spec Workflow
110 | - ❌ Forget to manage task progress, causing disorganized work
111 | - ❌ Not utilize Spec Workflow for systematic documentation
112 | - ❌ Unable to continuously track project status
113 | 
114 | With this configuration, AI will intelligently use Spec Workflow to manage the entire development process.
115 | 
116 | > **Configuration Note**: Please modify the following based on your needs:
117 | > 1. Change `./specs` to your preferred documentation directory path
118 | > 2. Change "English" to your preferred documentation language (e.g., "Chinese")
119 | 
120 | ```
121 | # Spec Workflow Usage Guidelines
122 | 
123 | ## 1. Check Project Progress
124 | When user mentions continuing previous project or is unsure about current progress, proactively use:
125 | specs-workflow tool with action.type="check" and path="./specs"
126 | 
127 | ## 2. Documentation Language
128 | All spec workflow documents should be written in English consistently, including all content in requirements, design, and task documents.
129 | 
130 | ## 3. Documentation Directory
131 | All spec workflow documents should be placed in ./specs directory to maintain consistent project documentation organization.
132 | 
133 | ## 4. Task Management
134 | Always use the following to manage task progress:
135 | specs-workflow tool with action.type="complete_task" and taskNumber="current task number"
136 | Follow the workflow guidance to continue working until all tasks are completed.
137 | 
138 | ## 5. Best Practices
139 | - Proactive progress check: When user says "continue from last time", first use check to see current status
140 | - Language consistency: Use the same language throughout all project documents
141 | - Flexible structure: Choose single-module or multi-module organization based on project scale
142 | - Task granularity: Each task should be completable within 1-2 hours
143 | ```
144 | 
145 | ## Installation
146 | 
147 | <details>
148 | <summary>📦 Installation Instructions</summary>
149 | 
150 | ### Requirements
151 | 
152 | - Node.js ≥ v18.0.0
153 | - npm or yarn
154 | - Claude Desktop or any MCP-compatible client
155 | 
156 | ### Install in Different MCP Clients
157 | 
158 | #### Claude Code (Recommended)
159 | 
160 | Use the Claude CLI to add the MCP server:
161 | 
162 | ```bash
163 | claude mcp add spec-workflow-mcp -s user -- npx -y spec-workflow-mcp@latest
164 | ```
165 | 
166 | #### Claude Desktop
167 | 
168 | Add to your Claude Desktop configuration:
169 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
170 | - Windows: `%APPDATA%/Claude/claude_desktop_config.json`
171 | - Linux: `~/.config/Claude/claude_desktop_config.json`
172 | 
173 | ```json
174 | {
175 |   "mcpServers": {
176 |     "spec-workflow": {
177 |       "command": "npx",
178 |       "args": ["-y", "spec-workflow-mcp@latest"]
179 |     }
180 |   }
181 | }
182 | ```
183 | 
184 | #### Cursor
185 | 
186 | Add to your Cursor configuration (`~/.cursor/config.json`):
187 | 
188 | ```json
189 | {
190 |   "mcpServers": {
191 |     "spec-workflow": {
192 |       "command": "npx",
193 |       "args": ["-y", "spec-workflow-mcp@latest"]
194 |     }
195 |   }
196 | }
197 | ```
198 | 
199 | #### Cline
200 | 
201 | Use Cline's MCP server management UI to add the server:
202 | 
203 | 1. Open VS Code with Cline extension
204 | 2. Open Cline settings (gear icon)
205 | 3. Navigate to MCP Servers section
206 | 4. Add new server with:
207 |    - Command: `npx`
208 |    - Arguments: `-y spec-workflow-mcp@latest`
209 | 
210 | #### Windsurf (Codeium)
211 | 
212 | Add to your Windsurf configuration (`~/.codeium/windsurf/mcp_config.json`):
213 | 
214 | ```json
215 | {
216 |   "mcpServers": {
217 |     "spec-workflow": {
218 |       "command": "npx",
219 |       "args": ["-y", "spec-workflow-mcp@latest"],
220 |       "env": {},
221 |       "autoApprove": [],
222 |       "disabled": false,
223 |       "timeout": 60,
224 |       "transportType": "stdio"
225 |     }
226 |   }
227 | }
228 | ```
229 | 
230 | #### VS Code (with MCP extension)
231 | 
232 | Add to your VS Code settings (`settings.json`):
233 | 
234 | ```json
235 | {
236 |   "mcp.servers": {
237 |     "spec-workflow": {
238 |       "command": "npx",
239 |       "args": ["-y", "spec-workflow-mcp@latest"]
240 |     }
241 |   }
242 | }
243 | ```
244 | 
245 | #### Zed
246 | 
247 | Add to your Zed configuration (`~/.config/zed/settings.json`):
248 | 
249 | ```json
250 | {
251 |   "assistant": {
252 |     "version": "2",
253 |     "mcp": {
254 |       "servers": {
255 |         "spec-workflow": {
256 |           "command": "npx",
257 |           "args": ["-y", "spec-workflow-mcp@latest"]
258 |         }
259 |       }
260 |     }
261 |   }
262 | }
263 | ```
264 | 
265 | ### Install from Source
266 | 
267 | ```bash
268 | git clone https://github.com/kingkongshot/specs-mcp.git
269 | cd specs-mcp
270 | npm install
271 | npm run build
272 | ```
273 | 
274 | Then add to Claude Desktop configuration:
275 | 
276 | ```json
277 | {
278 |   "mcpServers": {
279 |     "spec-workflow": {
280 |       "command": "node",
281 |       "args": ["/absolute/path/to/specs-mcp/dist/index.js"]
282 |     }
283 |   }
284 | }
285 | ```
286 | 
287 | </details>
288 | 
289 | 
290 | ## Links
291 | 
292 | - [GitHub Repository](https://github.com/kingkongshot/specs-mcp)
293 | - [NPM Package](https://www.npmjs.com/package/spec-workflow-mcp)
294 | - [Report Issues](https://github.com/kingkongshot/specs-mcp/issues)
295 | 
296 | ## License
297 | 
298 | MIT License
299 | 
300 | ---
301 | 
302 | <a href="https://glama.ai/mcp/servers/@kingkongshot/specs-workflow-mcp">
303 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/@kingkongshot/specs-workflow-mcp/badge" alt="Spec Workflow MCP server" />
304 | </a>
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "ES2022",
 5 |     "lib": ["ES2022"],
 6 |     "outDir": "./dist",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "resolveJsonModule": true,
13 |     "moduleResolution": "node",
14 |     "declaration": true,
15 |     "declarationMap": true,
16 |     "sourceMap": true,
17 |     "types": ["node"]
18 |   },
19 |   "include": ["src/**/*"],
20 |   "exclude": ["node_modules", "dist", "tests"]
21 | }
```

--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | import js from '@eslint/js';
 2 | import tseslint from '@typescript-eslint/eslint-plugin';
 3 | import tsParser from '@typescript-eslint/parser';
 4 | 
 5 | export default [
 6 |   {
 7 |     files: ['src/**/*.ts'],
 8 |     languageOptions: {
 9 |       parser: tsParser,
10 |       parserOptions: {
11 |         ecmaVersion: 2022,
12 |         sourceType: 'module',
13 |         project: './tsconfig.json'
14 |       }
15 |     },
16 |     plugins: {
17 |       '@typescript-eslint': tseslint
18 |     },
19 |     rules: {
20 |       ...js.configs.recommended.rules,
21 |       ...tseslint.configs.recommended.rules,
22 |       '@typescript-eslint/explicit-function-return-type': 'error',
23 |       '@typescript-eslint/no-unused-vars': 'error',
24 |       '@typescript-eslint/no-explicit-any': 'error'
25 |     }
26 |   },
27 |   {
28 |     ignores: ['dist/', 'node_modules/', '*.js', 'scripts/']
29 |   }
30 | ];
```

--------------------------------------------------------------------------------
/src/features/init/createRequirementsDoc.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Create requirements document
 3 |  */
 4 | 
 5 | import { writeFileSync, existsSync } from 'fs';
 6 | import { join } from 'path';
 7 | import { getRequirementsTemplate } from '../shared/documentTemplates.js';
 8 | 
 9 | export interface CreateResult {
10 |   generated: boolean;
11 |   message: string;
12 |   filePath?: string;
13 |   fileName?: string;
14 | }
15 | 
16 | export function createRequirementsDocument(
17 |   path: string,
18 |   featureName: string,
19 |   introduction: string
20 | ): CreateResult {
21 |   const fileName = 'requirements.md';
22 |   const filePath = join(path, fileName);
23 |   
24 |   if (existsSync(filePath)) {
25 |     return {
26 |       generated: false,
27 |       message: 'Requirements document already exists',
28 |       fileName,
29 |       filePath
30 |     };
31 |   }
32 |   
33 |   try {
34 |     const content = getRequirementsTemplate(featureName, introduction);
35 |     writeFileSync(filePath, content, 'utf-8');
36 |     
37 |     return {
38 |       generated: true,
39 |       message: 'Requirements document',
40 |       fileName,
41 |       filePath
42 |     };
43 |   } catch (error) {
44 |     return {
45 |       generated: false,
46 |       message: `Failed to create document: ${error}`,
47 |       fileName
48 |     };
49 |   }
50 | }
```

--------------------------------------------------------------------------------
/src/features/shared/documentAnalyzer.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Document content analyzer
 3 |  * Uses XML markers to detect if document has been edited
 4 |  */
 5 | 
 6 | import { readFileSync, existsSync } from 'fs';
 7 | 
 8 | // XML marker definitions - matching actual template placeholders
 9 | export const TEMPLATE_MARKERS = {
10 |   REQUIREMENTS_START: '<template-requirements>',
11 |   REQUIREMENTS_END: '</template-requirements>',
12 |   DESIGN_START: '<template-design>',
13 |   DESIGN_END: '</template-design>',
14 |   TASKS_START: '<template-tasks>',
15 |   TASKS_END: '</template-tasks>'
16 | };
17 | 
18 | /**
19 |  * Analyze if document has been edited
20 |  * Determined by checking if XML template markers exist
21 |  */
22 | export function isDocumentEdited(filePath: string): boolean {
23 |   if (!existsSync(filePath)) {
24 |     return false;
25 |   }
26 |   
27 |   try {
28 |     const content = readFileSync(filePath, 'utf-8');
29 |     
30 |     // Check if contains any template markers
31 |     const hasTemplateMarkers = Object.values(TEMPLATE_MARKERS).some(marker => 
32 |       content.includes(marker)
33 |     );
34 |     
35 |     // If no template markers, it means it has been edited
36 |     return !hasTemplateMarkers;
37 |   } catch {
38 |     return false;
39 |   }
40 | }
41 | 
42 | 
```

--------------------------------------------------------------------------------
/src/features/shared/documentUtils.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Shared utilities for document processing
 3 |  */
 4 | 
 5 | import { readFileSync } from 'fs';
 6 | 
 7 | export interface DocumentInfo {
 8 |   featureName: string;
 9 |   introduction: string;
10 | }
11 | 
12 | export function extractDocumentInfo(requirementsPath: string): DocumentInfo {
13 |   try {
14 |     const content = readFileSync(requirementsPath, 'utf-8');
15 |     const lines = content.split('\n');
16 |     
17 |     // Extract feature name
18 |     const titleLine = lines.find(line => line.startsWith('# '));
19 |     const featureName = titleLine 
20 |       ? titleLine.replace('# ', '').replace(' - Requirements Document', '').trim()
21 |       : 'Unnamed Feature';
22 |     
23 |     // Extract project background
24 |     const backgroundIndex = lines.findIndex(line => line.includes('## Project Background'));
25 |     let introduction = '';
26 |     
27 |     if (backgroundIndex !== -1) {
28 |       for (let i = backgroundIndex + 1; i < lines.length; i++) {
29 |         if (lines[i].startsWith('##')) break;
30 |         if (lines[i].trim()) {
31 |           introduction += lines[i] + '\n';
32 |         }
33 |       }
34 |     }
35 |     
36 |     return {
37 |       featureName,
38 |       introduction: introduction.trim() || 'No description'
39 |     };
40 |   } catch {
41 |     return {
42 |       featureName: 'Unnamed Feature',
43 |       introduction: 'No description'
44 |     };
45 |   }
46 | }
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "spec-workflow-mcp",
 3 |   "version": "1.0.8",
 4 |   "description": "MCP server for managing spec workflow (requirements, design, implementation)",
 5 |   "type": "module",
 6 |   "main": "dist/index.js",
 7 |   "bin": {
 8 |     "spec-workflow-mcp": "dist/index.js"
 9 |   },
10 |   "files": [
11 |     "dist/**/*",
12 |     "api/**/*"
13 |   ],
14 |   "scripts": {
15 |     "build": "tsc && chmod 755 dist/index.js && ./scripts/sync-package.sh",
16 |     "dev": "tsx src/index.ts",
17 |     "start": "node dist/index.js",
18 |     "lint": "eslint src",
19 |     "typecheck": "tsc --noEmit",
20 |     "debug": "./debug.sh",
21 |     "watch": "tsc --watch",
22 |     "inspector": "npx @modelcontextprotocol/inspector node dist/index.js",
23 |     "generate:types": "tsx scripts/generateTypes.ts",
24 |     "generate:webui": "tsx scripts/generateOpenApiWebUI.ts",
25 |     "sync:package": "./scripts/sync-package.sh",
26 |     "publish": "./scripts/publish-npm.sh"
27 |   },
28 |   "keywords": [
29 |     "mcp",
30 |     "workflow",
31 |     "spec"
32 |   ],
33 |   "author": "kingkongshot",
34 |   "license": "MIT",
35 |   "repository": {
36 |     "type": "git",
37 |     "url": "git+https://github.com/kingkongshot/specs-workflow-mcp.git"
38 |   },
39 |   "bugs": {
40 |     "url": "https://github.com/kingkongshot/specs-workflow-mcp/issues"
41 |   },
42 |   "homepage": "https://github.com/kingkongshot/specs-workflow-mcp#readme",
43 |   "dependencies": {
44 |     "@modelcontextprotocol/sdk": "^1.16.0",
45 |     "@types/js-yaml": "^4.0.9",
46 |     "js-yaml": "^4.1.0",
47 |     "zod": "^3.25.76"
48 |   },
49 |   "devDependencies": {
50 |     "@eslint/js": "^9.32.0",
51 |     "@types/node": "^22.10.5",
52 |     "@typescript-eslint/eslint-plugin": "^8.20.0",
53 |     "@typescript-eslint/parser": "^8.20.0",
54 |     "eslint": "^9.17.0",
55 |     "tsx": "^4.19.2",
56 |     "typescript": "^5.7.3"
57 |   }
58 | }
59 | 
```

--------------------------------------------------------------------------------
/scripts/publish-npm.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # Publish package to npm
 4 | # This script builds the project, generates the package, and publishes to npm
 5 | 
 6 | set -e
 7 | 
 8 | echo "🚀 Publishing to npm..."
 9 | 
10 | # Check if user is logged in to npm
11 | if ! npm whoami > /dev/null 2>&1; then
12 |     echo "❌ Error: Not logged in to npm"
13 |     echo "Please run: npm login"
14 |     exit 1
15 | fi
16 | 
17 | # Build the project (this will also generate the package directory)
18 | echo "🏗️ Building project..."
19 | npm run build
20 | 
21 | # Verify package directory exists
22 | if [ ! -d "package" ]; then
23 |     echo "❌ Error: Package directory not found"
24 |     echo "Build process may have failed"
25 |     exit 1
26 | fi
27 | 
28 | # Check if package already exists on npm
29 | PACKAGE_NAME=$(node -p "require('./package/package.json').name")
30 | PACKAGE_VERSION=$(node -p "require('./package/package.json').version")
31 | 
32 | echo "📦 Package: $PACKAGE_NAME@$PACKAGE_VERSION"
33 | 
34 | # Check if this version already exists
35 | if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version > /dev/null 2>&1; then
36 |     echo "⚠️ Warning: Version $PACKAGE_VERSION already exists on npm"
37 |     echo "Please update the version in package.json and try again"
38 |     exit 1
39 | fi
40 | 
41 | # Publish to npm
42 | echo "📤 Publishing to npm..."
43 | cd package
44 | 
45 | # Dry run first to check for issues
46 | echo "🔍 Running dry-run..."
47 | npm publish --dry-run
48 | 
49 | # Ask for confirmation
50 | echo ""
51 | read -p "🤔 Proceed with publishing $PACKAGE_NAME@$PACKAGE_VERSION? (y/N): " -n 1 -r
52 | echo ""
53 | 
54 | if [[ $REPLY =~ ^[Yy]$ ]]; then
55 |     # Actual publish
56 |     npm publish
57 |     
58 |     echo ""
59 |     echo "✅ Successfully published $PACKAGE_NAME@$PACKAGE_VERSION!"
60 |     echo "🔗 View on npm: https://www.npmjs.com/package/$PACKAGE_NAME"
61 |     echo ""
62 |     echo "📥 Install with:"
63 |     echo "  npm install -g $PACKAGE_NAME"
64 | else
65 |     echo "❌ Publishing cancelled"
66 |     exit 1
67 | fi
68 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | /**
 4 |  * MCP specification workflow server
 5 |  * Standard implementation based on MCP SDK
 6 |  */
 7 | 
 8 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
 9 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10 | import { specWorkflowTool } from './tools/specWorkflowTool.js';
11 | import { openApiLoader } from './features/shared/openApiLoader.js';
12 | import { readFileSync } from 'fs';
13 | import { fileURLToPath } from 'url';
14 | import { dirname, join } from 'path';
15 | 
16 | const __filename = fileURLToPath(import.meta.url);
17 | const __dirname = dirname(__filename);
18 | const packageJson = JSON.parse(
19 |   readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
20 | );
21 | 
22 | // Create server instance
23 | const server = new McpServer({
24 |   name: 'specs-workflow-mcp',
25 |   version: packageJson.version
26 | });
27 | 
28 | // Register tools
29 | specWorkflowTool.register(server);
30 | 
31 | // Start server
32 | async function main(): Promise<void> {
33 |   try {
34 |     // Initialize OpenAPI loader to ensure examples are cached
35 |     openApiLoader.loadSpec();
36 | 
37 |     const transport = new StdioServerTransport();
38 |     await server.connect(transport);
39 | 
40 |     // eslint-disable-next-line no-console
41 |     console.error('✨ MCP specification workflow server started');
42 |     // eslint-disable-next-line no-console
43 |     console.error(`📍 Version: ${packageJson.version} (Fully compliant with MCP best practices)`);
44 | 
45 |   } catch (error) {
46 |     // eslint-disable-next-line no-console
47 |     console.error('❌ Startup failed:', error);
48 |     // eslint-disable-next-line no-undef
49 |     process.exit(1);
50 |   }
51 | }
52 | 
53 | // Graceful shutdown
54 | // eslint-disable-next-line no-undef
55 | process.on('SIGINT', () => {
56 |   // eslint-disable-next-line no-console
57 |   console.error('\n👋 Server shutdown');
58 |   // eslint-disable-next-line no-undef
59 |   process.exit(0);
60 | });
61 | 
62 | main();
```

--------------------------------------------------------------------------------
/src/features/shared/progressCalculator.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Progress calculation
 3 |  */
 4 | 
 5 | import { WorkflowStatus } from './documentStatus.js';
 6 | import { getWorkflowConfirmations } from './confirmationStatus.js';
 7 | 
 8 | export interface WorkflowProgress {
 9 |   percentage: number;
10 |   completedStages: number;
11 |   totalStages: number;
12 |   details: {
13 |     requirements: StageProgress;
14 |     design: StageProgress;
15 |     tasks: StageProgress;
16 |   };
17 | }
18 | 
19 | interface StageProgress {
20 |   exists: boolean;
21 |   confirmed: boolean;
22 |   skipped: boolean;
23 | }
24 | 
25 | export function calculateWorkflowProgress(
26 |   path: string,
27 |   status: WorkflowStatus
28 | ): WorkflowProgress {
29 |   const confirmations = getWorkflowConfirmations(path);
30 |   
31 |   const details = {
32 |     requirements: getStageProgress(status.requirements, confirmations.confirmed.requirements, confirmations.skipped.requirements),
33 |     design: getStageProgress(status.design, confirmations.confirmed.design, confirmations.skipped.design),
34 |     tasks: getStageProgress(status.tasks, confirmations.confirmed.tasks, confirmations.skipped.tasks)
35 |   };
36 |   
37 |   const stages = [details.requirements, details.design, details.tasks];
38 |   const completedStages = stages.filter(s => s.confirmed || s.skipped).length;
39 |   const totalStages = stages.length;
40 |   
41 |   // Simplified progress calculation: each stage takes 1/3
42 |   // const stageProgress = 100 / totalStages; // \u672a\u4f7f\u7528
43 |   let totalProgress = 0;
44 |   
45 |   // Requirements stage: 30%
46 |   if (details.requirements.confirmed || details.requirements.skipped) {
47 |     totalProgress += 30;
48 |   }
49 |   
50 |   // Design stage: 30%
51 |   if (details.design.confirmed || details.design.skipped) {
52 |     totalProgress += 30;
53 |   }
54 |   
55 |   // Tasks stage: 40% (only if confirmed, not skipped)
56 |   // Skipping tasks doesn't count as progress since it's essential for development
57 |   if (details.tasks.confirmed) {
58 |     totalProgress += 40;
59 |   }
60 |   
61 |   return {
62 |     percentage: Math.round(totalProgress),
63 |     completedStages,
64 |     totalStages,
65 |     details
66 |   };
67 | }
68 | 
69 | function getStageProgress(
70 |   status: { exists: boolean },
71 |   confirmed: boolean,
72 |   skipped: boolean
73 | ): StageProgress {
74 |   return {
75 |     exists: status.exists,
76 |     confirmed,
77 |     skipped
78 |   };
79 | }
```

--------------------------------------------------------------------------------
/src/features/check/generateNextDocument.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Generate next stage document
 3 |  */
 4 | 
 5 | import { writeFileSync, existsSync } from 'fs';
 6 | import { join } from 'path';
 7 | import { WorkflowStage, getNextStage, getStageFileName } from '../shared/documentStatus.js';
 8 | import { getDesignTemplate, getTasksTemplate } from '../shared/documentTemplates.js';
 9 | import { extractDocumentInfo } from '../shared/documentUtils.js';
10 | 
11 | export interface NextDocumentResult {
12 |   generated: boolean;
13 |   alreadyExists?: boolean;
14 |   message: string;
15 |   fileName?: string;
16 |   filePath?: string;
17 |   guide?: unknown;
18 | }
19 | 
20 | export async function generateNextDocument(
21 |   path: string,
22 |   currentStage: WorkflowStage
23 | ): Promise<NextDocumentResult> {
24 |   const nextStage = getNextStage(currentStage);
25 |   
26 |   if (nextStage === 'completed') {
27 |     return {
28 |       generated: false,
29 |       message: 'All documents completed'
30 |     };
31 |   }
32 |   
33 |   const fileName = getStageFileName(nextStage);
34 |   const filePath = join(path, fileName);
35 |   
36 |   if (existsSync(filePath)) {
37 |     return {
38 |       generated: false,
39 |       alreadyExists: true,
40 |       message: `${fileName} already exists`,
41 |       fileName,
42 |       filePath
43 |     };
44 |   }
45 |   
46 |   // Extract feature information
47 |   const documentInfo = extractDocumentInfo(join(path, 'requirements.md'));
48 |   
49 |   // Generate document content
50 |   let content: string;
51 |   
52 |   switch (nextStage) {
53 |     case 'design':
54 |       content = getDesignTemplate(documentInfo.featureName);
55 |       // guideType = 'design'; // \u672a\u4f7f\u7528
56 |       break;
57 |     case 'tasks':
58 |       content = getTasksTemplate(documentInfo.featureName);
59 |       // guideType = 'implementation'; // \u672a\u4f7f\u7528
60 |       break;
61 |     default:
62 |       return {
63 |         generated: false,
64 |         message: `Unknown document type: ${nextStage}`
65 |       };
66 |   }
67 |   
68 |   try {
69 |     writeFileSync(filePath, content, 'utf-8');
70 |     
71 |     return {
72 |       generated: true,
73 |       message: `Generated ${fileName}`,
74 |       fileName,
75 |       filePath,
76 |       guide: undefined // Guide resources are now handled via OpenAPI shared resources
77 |     };
78 |   } catch (error) {
79 |     return {
80 |       generated: false,
81 |       message: `Failed to generate document: ${error}`
82 |     };
83 |   }
84 | }
85 | 
86 | 
```

--------------------------------------------------------------------------------
/src/features/shared/documentStatus.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Document status management related functions
 3 |  */
 4 | 
 5 | import { existsSync } from 'fs';
 6 | import { join } from 'path';
 7 | import { isStageSkipped, isStageConfirmed } from './confirmationStatus.js';
 8 | 
 9 | export interface DocumentStatus {
10 |   exists: boolean;
11 | }
12 | 
13 | export interface WorkflowStatus {
14 |   requirements: DocumentStatus;
15 |   design: DocumentStatus;
16 |   tasks: DocumentStatus;
17 | }
18 | 
19 | export type WorkflowStage = 'requirements' | 'design' | 'tasks' | 'completed';
20 | 
21 | export function getWorkflowStatus(path: string): WorkflowStatus {
22 |   return {
23 |     requirements: getDocumentStatus(path, 'requirements.md'),
24 |     design: getDocumentStatus(path, 'design.md'),
25 |     tasks: getDocumentStatus(path, 'tasks.md')
26 |   };
27 | }
28 | 
29 | function getDocumentStatus(path: string, fileName: string): DocumentStatus {
30 |   const filePath = join(path, fileName);
31 |   return { exists: existsSync(filePath) };
32 | }
33 | 
34 | 
35 | export function getCurrentStage(status: WorkflowStatus, path?: string): WorkflowStage {
36 |   if (!path) {
37 |     // Backward compatibility: if no path, return the first existing document stage
38 |     if (status.requirements.exists) return 'requirements';
39 |     if (status.design.exists) return 'design';
40 |     if (status.tasks.exists) return 'tasks';
41 |     return 'completed';
42 |   }
43 |   
44 |   // Determine current stage based on confirmations
45 |   // If requirements stage is not confirmed and not skipped, current stage is requirements
46 |   if (!isStageConfirmed(path, 'requirements') && !isStageSkipped(path, 'requirements')) {
47 |     return 'requirements';
48 |   }
49 |   
50 |   // If design stage is not confirmed and not skipped, current stage is design
51 |   if (!isStageConfirmed(path, 'design') && !isStageSkipped(path, 'design')) {
52 |     return 'design';
53 |   }
54 |   
55 |   // If tasks stage is not confirmed and not skipped, current stage is tasks
56 |   if (!isStageConfirmed(path, 'tasks') && !isStageSkipped(path, 'tasks')) {
57 |     return 'tasks';
58 |   }
59 |   
60 |   return 'completed';
61 | }
62 | 
63 | export function getNextStage(stage: WorkflowStage): WorkflowStage {
64 |   const stages: WorkflowStage[] = ['requirements', 'design', 'tasks', 'completed'];
65 |   const index = stages.indexOf(stage);
66 |   return stages[Math.min(index + 1, stages.length - 1)];
67 | }
68 | 
69 | export function getStageName(stage: string): string {
70 |   const names: Record<string, string> = {
71 |     requirements: 'Requirements Document',
72 |     design: 'Design Document',
73 |     tasks: 'Task List',
74 |     completed: 'Completed'
75 |   };
76 |   return names[stage] || stage;
77 | }
78 | 
79 | export function getStageFileName(stage: string): string {
80 |   const fileNames: Record<string, string> = {
81 |     requirements: 'requirements.md',
82 |     design: 'design.md',
83 |     tasks: 'tasks.md'
84 |   };
85 |   return fileNames[stage] || '';
86 | }
```

--------------------------------------------------------------------------------
/src/features/shared/confirmationStatus.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Confirmation status management
  3 |  */
  4 | 
  5 | import { readFileSync, writeFileSync, existsSync } from 'fs';
  6 | import { join } from 'path';
  7 | 
  8 | export interface ConfirmationStatus {
  9 |   requirements: boolean;
 10 |   design: boolean;
 11 |   tasks: boolean;
 12 | }
 13 | 
 14 | export interface SkipStatus {
 15 |   requirements: boolean;
 16 |   design: boolean;
 17 |   tasks: boolean;
 18 | }
 19 | 
 20 | export interface WorkflowConfirmations {
 21 |   confirmed: ConfirmationStatus;
 22 |   skipped: SkipStatus;
 23 | }
 24 | 
 25 | const CONFIRMATION_FILE = '.workflow-confirmations.json';
 26 | 
 27 | export function getWorkflowConfirmations(path: string): WorkflowConfirmations {
 28 |   const filePath = join(path, CONFIRMATION_FILE);
 29 |   
 30 |   const defaultStatus: WorkflowConfirmations = {
 31 |     confirmed: {
 32 |       requirements: false,
 33 |       design: false,
 34 |       tasks: false
 35 |     },
 36 |     skipped: {
 37 |       requirements: false,
 38 |       design: false,
 39 |       tasks: false
 40 |     }
 41 |   };
 42 |   
 43 |   if (!existsSync(filePath)) {
 44 |     return defaultStatus;
 45 |   }
 46 |   
 47 |   try {
 48 |     const content = readFileSync(filePath, 'utf-8');
 49 |     const parsed = JSON.parse(content);
 50 |     
 51 |     // Compatible with old format
 52 |     if (!parsed.confirmed && !parsed.skipped) {
 53 |       return {
 54 |         confirmed: parsed,
 55 |         skipped: {
 56 |           requirements: false,
 57 |           design: false,
 58 |           tasks: false
 59 |         }
 60 |       };
 61 |     }
 62 |     
 63 |     return parsed;
 64 |   } catch {
 65 |     return defaultStatus;
 66 |   }
 67 | }
 68 | 
 69 | // Keep old function for compatibility with existing code
 70 | export function getConfirmationStatus(path: string): ConfirmationStatus {
 71 |   const confirmations = getWorkflowConfirmations(path);
 72 |   return confirmations.confirmed;
 73 | }
 74 | 
 75 | export function updateStageConfirmation(
 76 |   path: string, 
 77 |   stage: keyof ConfirmationStatus, 
 78 |   confirmed: boolean
 79 | ): void {
 80 |   const confirmations = getWorkflowConfirmations(path);
 81 |   confirmations.confirmed[stage] = confirmed;
 82 |   
 83 |   const filePath = join(path, CONFIRMATION_FILE);
 84 |   writeFileSync(filePath, JSON.stringify(confirmations, null, 2));
 85 | }
 86 | 
 87 | export function updateStageSkipped(
 88 |   path: string, 
 89 |   stage: keyof SkipStatus, 
 90 |   skipped: boolean
 91 | ): void {
 92 |   const confirmations = getWorkflowConfirmations(path);
 93 |   confirmations.skipped[stage] = skipped;
 94 |   
 95 |   const filePath = join(path, CONFIRMATION_FILE);
 96 |   writeFileSync(filePath, JSON.stringify(confirmations, null, 2));
 97 | }
 98 | 
 99 | export function isStageConfirmed(
100 |   path: string, 
101 |   stage: keyof ConfirmationStatus
102 | ): boolean {
103 |   const confirmations = getWorkflowConfirmations(path);
104 |   return confirmations.confirmed[stage];
105 | }
106 | 
107 | export function isStageSkipped(
108 |   path: string, 
109 |   stage: keyof SkipStatus
110 | ): boolean {
111 |   const confirmations = getWorkflowConfirmations(path);
112 |   return confirmations.skipped[stage];
113 | }
```

--------------------------------------------------------------------------------
/src/features/shared/mcpTypes.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * MCP (Model Context Protocol) compatible type definitions
  3 |  * Standard interfaces conforming to MCP specification
  4 |  */
  5 | 
  6 | /**
  7 |  * MCP text content
  8 |  */
  9 | export interface McpTextContent {
 10 |   type: "text";
 11 |   text: string;
 12 | }
 13 | 
 14 | /**
 15 |  * MCP image content
 16 |  */
 17 | export interface McpImageContent {
 18 |   type: "image";
 19 |   data: string;      // base64 encoded
 20 |   mimeType: string;  // e.g. "image/png"
 21 | }
 22 | 
 23 | /**
 24 |  * MCP audio content
 25 |  */
 26 | export interface McpAudioContent {
 27 |   type: "audio";
 28 |   data: string;      // base64 encoded
 29 |   mimeType: string;  // e.g. "audio/mp3"
 30 | }
 31 | 
 32 | /**
 33 |  * MCP resource content
 34 |  */
 35 | export interface McpResourceContent {
 36 |   type: "resource";
 37 |   resource: {
 38 |     uri: string;       // Resource URI
 39 |     title?: string;    // Optional title
 40 |     mimeType: string;  // MIME type
 41 |     text?: string;     // Optional text content
 42 |   };
 43 | }
 44 | 
 45 | /**
 46 |  * MCP content type union
 47 |  */
 48 | export type McpContent = 
 49 |   | McpTextContent 
 50 |   | McpImageContent 
 51 |   | McpAudioContent 
 52 |   | McpResourceContent;
 53 | 
 54 | /**
 55 |  * MCP tool call result
 56 |  * This is the standard format that must be returned after tool execution
 57 |  */
 58 | export interface McpCallToolResult {
 59 |   content: McpContent[];
 60 |   isError?: boolean;
 61 |   structuredContent?: unknown; // Used when outputSchema is defined
 62 | }
 63 | 
 64 | /**
 65 |  * Internally used workflow result
 66 |  * Used to pass data between functional modules
 67 |  */
 68 | export interface WorkflowResult {
 69 |   displayText: string;
 70 |   data: {
 71 |     success?: boolean;
 72 |     error?: string;
 73 |     [key: string]: unknown;
 74 |   };
 75 |   resources?: Array<{
 76 |     uri: string;
 77 |     title?: string;
 78 |     mimeType: string;
 79 |     text?: string;
 80 |   }>;
 81 | }
 82 | 
 83 | /**
 84 |  * Create text content
 85 |  */
 86 | export function createTextContent(text: string): McpTextContent {
 87 |   return {
 88 |     type: "text",
 89 |     text
 90 |   };
 91 | }
 92 | 
 93 | /**
 94 |  * Create resource content
 95 |  */
 96 | export function createResourceContent(resource: McpResourceContent['resource']): McpResourceContent {
 97 |   return {
 98 |     type: "resource",
 99 |     resource
100 |   };
101 | }
102 | 
103 | /**
104 |  * Convert internal workflow result to MCP format
105 |  */
106 | export function toMcpResult(result: WorkflowResult): McpCallToolResult {
107 |   const content: McpContent[] = [
108 |     createTextContent(result.displayText)
109 |   ];
110 |   
111 |   // Resources are now embedded in displayText, no need to add them separately
112 |   // This avoids duplicate display in clients that support resource content type
113 |   
114 |   return {
115 |     content,
116 |     isError: result.data.success === false,
117 |     // Add structured content, return response object conforming to OpenAPI specification
118 |     structuredContent: result.data && typeof result.data === 'object' && 'displayText' in result.data 
119 |       ? result.data  // If data is already a complete response object
120 |       : undefined    // Otherwise don't return structuredContent
121 |   };
122 | }
```

--------------------------------------------------------------------------------
/src/features/executeWorkflow.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Workflow execution entry point
  3 |  */
  4 | 
  5 | import { existsSync } from 'fs';
  6 | import { getWorkflowStatus, getCurrentStage } from './shared/documentStatus.js';
  7 | import { initWorkflow } from './init/initWorkflow.js';
  8 | import { checkWorkflow } from './check/checkWorkflow.js';
  9 | import { skipStage } from './skip/skipStage.js';
 10 | import { confirmStage } from './confirm/confirmStage.js';
 11 | import { completeTask } from './task/completeTask.js';
 12 | import { WorkflowResult } from './shared/mcpTypes.js';
 13 | 
 14 | export interface WorkflowArgs {
 15 |   path: string;
 16 |   action?: {
 17 |     type: string;
 18 |     featureName?: string;
 19 |     introduction?: string;
 20 |     taskNumber?: string | string[];
 21 |   };
 22 | }
 23 | 
 24 | export async function executeWorkflow(
 25 |   args: WorkflowArgs,
 26 |   onProgress?: (progress: number, total: number, message: string) => Promise<void>
 27 | ): Promise<WorkflowResult> {
 28 |   const { path, action } = args;
 29 |   
 30 |   if (!action) {
 31 |     return getStatus(path);
 32 |   }
 33 |   
 34 |   switch (action.type) {
 35 |     case 'init':
 36 |       if (!action.featureName || !action.introduction) {
 37 |         return {
 38 |           displayText: '❌ Initialization requires featureName and introduction parameters',
 39 |           data: {
 40 |             success: false,
 41 |             error: 'Missing required parameters'
 42 |           }
 43 |         };
 44 |       }
 45 |       return initWorkflow({
 46 |         path,
 47 |         featureName: action.featureName,
 48 |         introduction: action.introduction,
 49 |         onProgress
 50 |       });
 51 |       
 52 |     case 'check':
 53 |       return checkWorkflow({ path, onProgress });
 54 |       
 55 |     case 'skip':
 56 |       return skipStage({ path });
 57 |       
 58 |     case 'confirm':
 59 |       return confirmStage({ path });
 60 |       
 61 |     case 'complete_task':
 62 |       if (!action.taskNumber) {
 63 |         return {
 64 |           displayText: '❌ Completing task requires taskNumber parameter',
 65 |           data: {
 66 |             success: false,
 67 |             error: 'Missing required parameters'
 68 |           }
 69 |         };
 70 |       }
 71 |       return completeTask({
 72 |         path,
 73 |         taskNumber: action.taskNumber
 74 |       });
 75 |       
 76 |     default:
 77 |       return {
 78 |         displayText: `❌ Unknown operation type: ${action.type}`,
 79 |         data: {
 80 |           success: false,
 81 |           error: `Unknown operation type: ${action.type}`
 82 |         }
 83 |       };
 84 |   }
 85 | }
 86 | 
 87 | function getStatus(path: string): WorkflowResult {
 88 |   if (!existsSync(path)) {
 89 |     return {
 90 |       displayText: `📁 Directory does not exist
 91 | 
 92 | Please use init operation to initialize:
 93 | \`\`\`json
 94 | {
 95 |   "action": {
 96 |     "type": "init",
 97 |     "featureName": "Feature name",
 98 |     "introduction": "Feature description"
 99 |   }
100 | }
101 | \`\`\``,
102 |       data: {
103 |         message: 'Directory does not exist, initialization required'
104 |       }
105 |     };
106 |   }
107 |   
108 |   const status = getWorkflowStatus(path);
109 |   const stage = getCurrentStage(status, path);
110 |   
111 |   return {
112 |     displayText: `📊 Current status
113 | 
114 | Available operations:
115 | - check: Check current stage
116 | - skip: Skip current stage`,
117 |     data: {
118 |       message: 'Please select an operation',
119 |       stage
120 |     }
121 |   };
122 | }
```

--------------------------------------------------------------------------------
/src/features/shared/documentTemplates.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Document templates - using OpenAPI as single source of truth
  3 |  */
  4 | 
  5 | import { openApiLoader } from './openApiLoader.js';
  6 | import { isObject, isArray } from './typeGuards.js';
  7 | 
  8 | // Format template, replace variables
  9 | function formatTemplate(template: unknown, values: { [key: string]: unknown }): string {
 10 |   const lines: string[] = [];
 11 |   
 12 |   if (!isObject(template)) {
 13 |     throw new Error('Invalid template format');
 14 |   }
 15 |   
 16 |   const title = template.title;
 17 |   if (typeof title === 'string') {
 18 |     lines.push(`# ${interpolate(title, values)}`);
 19 |     lines.push('');
 20 |   }
 21 |   
 22 |   const sections = template.sections;
 23 |   if (isArray(sections)) {
 24 |     for (const section of sections) {
 25 |       if (isObject(section)) {
 26 |         if (typeof section.content === 'string') {
 27 |           lines.push(interpolate(section.content, values));
 28 |         } else if (typeof section.placeholder === 'string') {
 29 |           lines.push(section.placeholder);
 30 |         }
 31 |         lines.push('');
 32 |       }
 33 |     }
 34 |   }
 35 |   
 36 |   return lines.join('\n');
 37 | }
 38 | 
 39 | // Variable interpolation
 40 | function interpolate(template: string, values: { [key: string]: unknown }): string {
 41 |   return template.replace(/\${([^}]+)}/g, (match, key) => {
 42 |     const keys = key.split('.');
 43 |     let value: unknown = values;
 44 |     
 45 |     for (const k of keys) {
 46 |       if (isObject(value) && k in value) {
 47 |         value = value[k];
 48 |       } else {
 49 |         return match;
 50 |       }
 51 |     }
 52 |     
 53 |     return String(value);
 54 |   });
 55 | }
 56 | 
 57 | // Get requirements document template
 58 | export function getRequirementsTemplate(featureName: string, introduction: string): string {
 59 |   // Ensure spec is loaded
 60 |   openApiLoader.loadSpec();
 61 |   const template = openApiLoader.getDocumentTemplate('requirements');
 62 |   if (!template) {
 63 |     throw new Error('Requirements template not found in OpenAPI specification');
 64 |   }
 65 | 
 66 |   return formatTemplate(template, { featureName, introduction });
 67 | }
 68 | 
 69 | // Get design document template
 70 | export function getDesignTemplate(featureName: string): string {
 71 |   // Ensure spec is loaded
 72 |   openApiLoader.loadSpec();
 73 |   const template = openApiLoader.getDocumentTemplate('design');
 74 |   if (!template) {
 75 |     throw new Error('Design template not found in OpenAPI specification');
 76 |   }
 77 | 
 78 |   return formatTemplate(template, { featureName });
 79 | }
 80 | 
 81 | // Get tasks list template
 82 | export function getTasksTemplate(featureName: string): string {
 83 |   // Ensure spec is loaded
 84 |   openApiLoader.loadSpec();
 85 |   const template = openApiLoader.getDocumentTemplate('tasks');
 86 |   if (!template) {
 87 |     throw new Error('Tasks template not found in OpenAPI specification');
 88 |   }
 89 | 
 90 |   return formatTemplate(template, { featureName });
 91 | }
 92 | 
 93 | // Get skipped marker template
 94 | export function getSkippedTemplate(featureName: string, stageName: string): string {
 95 |   // Ensure spec is loaded
 96 |   openApiLoader.loadSpec();
 97 |   const template = openApiLoader.getDocumentTemplate('skipped');
 98 |   if (!template) {
 99 |     throw new Error('Skipped template not found in OpenAPI specification');
100 |   }
101 | 
102 |   return formatTemplate(template, { featureName, stageName });
103 | }
```

--------------------------------------------------------------------------------
/src/features/shared/openApiTypes.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // Auto-generated type definitions - do not modify manually
  2 | // Generated from api/spec-workflow.openapi.yaml
  3 | 
  4 | export interface WorkflowRequest {
  5 |   path: string;  // Specification directory path (e.g., /Users/link/specs-mcp/batch-log-test)
  6 |   action: Action;
  7 | }
  8 | 
  9 | export interface Action {
 10 |   type: 'init' | 'check' | 'skip' | 'confirm' | 'complete_task';
 11 |   featureName?: string;  // Feature name (required for init)
 12 |   introduction?: string;  // Feature introduction (required for init)
 13 |   taskNumber?: string | string[];  // Task number(s) to mark as completed (required for complete_task)
 14 | }
 15 | 
 16 | export interface WorkflowResponse {
 17 |   result: InitResponse | CheckResponse | SkipResponse | ConfirmResponse | BatchCompleteTaskResponse;
 18 | }
 19 | 
 20 | export interface InitResponse {
 21 |   success: boolean;
 22 |   data: { path: string; featureName: string; nextAction: 'edit_requirements' };
 23 |   displayText: string;
 24 |   resources?: ResourceRef[];
 25 | }
 26 | 
 27 | export interface CheckResponse {
 28 |   stage: Stage;
 29 |   progress: Progress;
 30 |   status: Status;
 31 |   displayText: string;
 32 |   resources?: ResourceRef[];
 33 | }
 34 | 
 35 | export interface SkipResponse {
 36 |   stage: string;
 37 |   skipped: boolean;
 38 |   progress: Progress;
 39 |   displayText: string;
 40 |   resources?: ResourceRef[];
 41 | }
 42 | 
 43 | export interface ConfirmResponse {
 44 |   stage: string;
 45 |   confirmed: boolean;
 46 |   nextStage: string;
 47 |   progress: Progress;
 48 |   displayText: string;
 49 |   resources?: ResourceRef[];
 50 | }
 51 | 
 52 | 
 53 | 
 54 | export interface BatchCompleteTaskResponse {
 55 |   success: boolean;  // Whether the batch operation succeeded
 56 |   completedTasks?: string[];  // Task numbers that were actually completed in this operation
 57 |   alreadyCompleted?: string[];  // Task numbers that were already completed before this operation
 58 |   failedTasks?: { taskNumber: string; reason: string }[];  // Tasks that could not be completed with reasons
 59 |   results?: { taskNumber: string; success: boolean; status: 'completed' | 'already_completed' | 'failed' }[];  // Detailed results for each task in the batch
 60 |   nextTask?: { number: string; description: string };  // Information about the next uncompleted task
 61 |   hasNextTask?: boolean;  // Whether there are more tasks to complete
 62 |   displayText: string;  // Human-readable message about the batch operation
 63 | }
 64 | 
 65 | export interface Stage {
 66 | }
 67 | 
 68 | export interface Progress {
 69 |   overall: number;  // Overall progress percentage
 70 |   requirements: number;  // Requirements phase progress
 71 |   design: number;  // Design phase progress
 72 |   tasks: number;  // Tasks phase progress
 73 | }
 74 | 
 75 | export interface Status {
 76 |   type: 'not_started' | 'not_edited' | 'in_progress' | 'ready_to_confirm' | 'confirmed' | 'completed';
 77 |   reason?: string;
 78 |   readyToConfirm?: boolean;
 79 | }
 80 | 
 81 | export interface Resource {
 82 |   id: string;  // Resource identifier
 83 |   content: string;  // Resource content (Markdown format)
 84 | }
 85 | 
 86 | export interface ResourceRef {
 87 |   uri: string;  // Resource URI
 88 |   title?: string;  // Optional resource title
 89 |   mimeType: string;  // Resource MIME type
 90 |   text?: string;  // Optional resource text content
 91 | }
 92 | 
 93 | // Extended type definitions
 94 | export interface ErrorResponse {
 95 |   displayText: string;
 96 |   variables?: Record<string, any>;
 97 | }
 98 | 
 99 | export interface ContentCheckRules {
100 |   minLength?: number;
101 |   requiredSections?: string[];
102 |   optionalSections?: string[];
103 |   minTasks?: number;
104 |   taskFormat?: string;
105 |   requiresEstimate?: boolean;
106 | }
```

--------------------------------------------------------------------------------
/scripts/sync-package.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/bash
  2 | 
  3 | # Generate package directory with all necessary files for npm publishing
  4 | # This script creates a complete package directory from scratch
  5 | 
  6 | set -e
  7 | 
  8 | echo "📦 Generating package directory..."
  9 | 
 10 | # Create package directory structure
 11 | echo "📁 Creating directory structure..."
 12 | rm -rf package
 13 | mkdir -p package/api
 14 | mkdir -p package/dist
 15 | 
 16 | # Generate package.json
 17 | echo "📄 Generating package.json..."
 18 | MAIN_VERSION=$(node -p "require('./package.json').version")
 19 | cat > package/package.json << EOF
 20 | {
 21 |   "name": "spec-workflow-mcp",
 22 |   "version": "$MAIN_VERSION",
 23 |   "description": "MCP server for managing spec workflow (requirements, design, implementation)",
 24 |   "type": "module",
 25 |   "main": "dist/index.js",
 26 |   "bin": {
 27 |     "spec-workflow-mcp": "dist/index.js"
 28 |   },
 29 |   "files": [
 30 |     "dist/**/*",
 31 |     "api/**/*"
 32 |   ],
 33 |   "keywords": [
 34 |     "mcp",
 35 |     "workflow",
 36 |     "spec",
 37 |     "requirements",
 38 |     "design",
 39 |     "implementation",
 40 |     "openapi"
 41 |   ],
 42 |   "author": "kingkongshot",
 43 |   "license": "MIT",
 44 |   "repository": {
 45 |     "type": "git",
 46 |     "url": "git+https://github.com/kingkongshot/specs-workflow-mcp.git"
 47 |   },
 48 |   "bugs": {
 49 |     "url": "https://github.com/kingkongshot/specs-workflow-mcp/issues"
 50 |   },
 51 |   "homepage": "https://github.com/kingkongshot/specs-workflow-mcp#readme",
 52 |   "dependencies": {
 53 |     "@modelcontextprotocol/sdk": "^1.0.6",
 54 |     "@types/js-yaml": "^4.0.9",
 55 |     "js-yaml": "^4.1.0",
 56 |     "zod": "^3.25.76"
 57 |   },
 58 |   "engines": {
 59 |     "node": ">=18.0.0"
 60 |   }
 61 | }
 62 | EOF
 63 | 
 64 | # Generate README.md
 65 | echo "📖 Generating README.md..."
 66 | cat > package/README.md << 'EOF'
 67 | # Spec Workflow MCP
 68 | 
 69 | A Model Context Protocol (MCP) server for managing specification workflows including requirements, design, and implementation phases.
 70 | 
 71 | ## Features
 72 | 
 73 | - **Requirements Management**: Create and validate requirement documents
 74 | - **Design Documentation**: Generate and review design specifications
 75 | - **Task Management**: Break down implementation into manageable tasks
 76 | - **Progress Tracking**: Monitor workflow progress across all phases
 77 | - **OpenAPI Integration**: Full OpenAPI 3.1.0 specification support
 78 | 
 79 | ## Installation
 80 | 
 81 | ```bash
 82 | npm install -g spec-workflow-mcp
 83 | ```
 84 | 
 85 | ## Usage
 86 | 
 87 | ### As MCP Server
 88 | 
 89 | Add to your MCP client configuration:
 90 | 
 91 | ```json
 92 | {
 93 |   "mcpServers": {
 94 |     "specs-workflow": {
 95 |       "command": "spec-workflow-mcp"
 96 |     }
 97 |   }
 98 | }
 99 | ```
100 | 
101 | ### Available Operations
102 | 
103 | - `init` - Initialize a new feature specification
104 | - `check` - Check current workflow status
105 | - `confirm` - Confirm stage completion
106 | - `skip` - Skip current stage
107 | - `complete_task` - Mark tasks as completed
108 | 
109 | ## Documentation
110 | 
111 | For detailed usage instructions and examples, visit the [GitHub repository](https://github.com/kingkongshot/specs-workflow-mcp).
112 | 
113 | ## License
114 | 
115 | MIT
116 | EOF
117 | 
118 | # Copy OpenAPI specification
119 | echo "📋 Copying OpenAPI specification..."
120 | cp api/spec-workflow.openapi.yaml package/api/spec-workflow.openapi.yaml
121 | 
122 | # Copy built files
123 | echo "🏗️ Copying built files..."
124 | if [ -d "dist" ]; then
125 |     cp -r dist/* package/dist/
126 | else
127 |     echo "❌ Error: dist directory not found. Run 'npm run build' first."
128 |     exit 1
129 | fi
130 | 
131 | echo "✅ Package directory generated successfully!"
132 | echo "📦 Version: $MAIN_VERSION"
133 | echo "📁 Location: ./package/"
134 | echo ""
135 | echo "Contents:"
136 | echo "  📄 package.json"
137 | echo "  📖 README.md"
138 | echo "  📋 api/spec-workflow.openapi.yaml"
139 | echo "  🏗️ dist/ (compiled JavaScript)"
140 | 
```

--------------------------------------------------------------------------------
/src/tools/specWorkflowTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Intelligent specification workflow tool
 3 |  * Implementation fully compliant with MCP best practices
 4 |  */
 5 | 
 6 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
 7 | import { z } from 'zod';
 8 | import { executeWorkflow } from '../features/executeWorkflow.js';
 9 | import { toMcpResult } from '../features/shared/mcpTypes.js';
10 | 
11 | // Input parameter Schema
12 | const inputSchema = {
13 |   path: z.string().describe('Specification directory path (e.g., /Users/link/specs-mcp/batch-log-test)'),
14 |   action: z.object({
15 |     type: z.enum(['init', 'check', 'skip', 'confirm', 'complete_task']).describe('Operation type'),
16 |     featureName: z.string().optional().describe('Feature name (required for init)'),
17 |     introduction: z.string().optional().describe('Feature introduction (required for init)'),
18 |     taskNumber: z.union([
19 |       z.string(),
20 |       z.array(z.string())
21 |     ]).optional().describe('Task number(s) to mark as completed (required for complete_task). Can be a single string or an array of strings')
22 |   }).optional().describe('Operation parameters')
23 | };
24 | 
25 | export const specWorkflowTool = {
26 |   /**
27 |    * Register tool to MCP server
28 |    */
29 |   register(server: McpServer): void {
30 |     server.registerTool(
31 |       'specs-workflow',
32 |       {
33 |         title: 'Intelligent Specification Workflow Tool',  // Added title property
34 |         description: 'Manage intelligent writing workflow for software project requirements, design, and task documents. Supports initialization, checking, skipping, confirmation, and task completion operations (single or batch).',
35 |         inputSchema,
36 |         annotations: {
37 |           progressReportingHint: true,
38 |           longRunningHint: true,
39 |           readOnlyHint: false,      // This tool modifies files
40 |           idempotentHint: false     // Operation is not idempotent
41 |         }
42 |       },
43 |       // eslint-disable-next-line @typescript-eslint/no-unused-vars
44 |       async (args, _extra) => {
45 |         try {
46 |           // Temporarily not using progress callback, as MCP SDK type definitions may differ
47 |           const onProgress = undefined;
48 |           
49 |           // Execute workflow
50 |           const workflowResult = await executeWorkflow({
51 |             path: args.path,
52 |             action: args.action
53 |           }, onProgress);
54 |           
55 |           // Use standard MCP format converter
56 |           const mcpResult = toMcpResult(workflowResult);
57 |           
58 |           // Return format that meets SDK requirements, including structuredContent
59 |           const callToolResult: Record<string, unknown> = {
60 |             content: mcpResult.content,
61 |             isError: mcpResult.isError
62 |           };
63 |           
64 |           if (mcpResult.structuredContent !== undefined) {
65 |             callToolResult.structuredContent = mcpResult.structuredContent;
66 |           }
67 |           
68 |           // Type assertion to satisfy MCP SDK requirements
69 |           return callToolResult as {
70 |             content: Array<{
71 |               type: 'text';
72 |               text: string;
73 |               [x: string]: unknown;
74 |             }>;
75 |             isError?: boolean;
76 |             [x: string]: unknown;
77 |           };
78 |           
79 |         } catch (error) {
80 |           // Error handling must also comply with MCP format
81 |           return {
82 |             content: [{
83 |               type: 'text' as const,
84 |               text: `Execution failed: ${error instanceof Error ? error.message : String(error)}`
85 |             }],
86 |             isError: true
87 |           };
88 |         }
89 |       }
90 |     );
91 |   }
92 | };
```

--------------------------------------------------------------------------------
/src/features/check/checkWorkflow.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Check workflow status
  3 |  */
  4 | 
  5 | import { existsSync, readFileSync } from 'fs';
  6 | import { join } from 'path';
  7 | import { getWorkflowStatus, getCurrentStage } from '../shared/documentStatus.js';
  8 | import { calculateWorkflowProgress } from '../shared/progressCalculator.js';
  9 | import { analyzeStage } from './analyzeStage.js';
 10 | import { generateNextDocument } from './generateNextDocument.js';
 11 | import { responseBuilder } from '../shared/responseBuilder.js';
 12 | import { WorkflowResult } from '../shared/mcpTypes.js';
 13 | import { parseTasksFile, getFirstUncompletedTask, formatTaskForFullDisplay } from '../shared/taskParser.js';
 14 | 
 15 | export interface CheckOptions {
 16 |   path: string;
 17 |   onProgress?: (progress: number, total: number, message: string) => Promise<void>;
 18 | }
 19 | 
 20 | export async function checkWorkflow(options: CheckOptions): Promise<WorkflowResult> {
 21 |   const { path, onProgress } = options;
 22 |   
 23 |   if (!existsSync(path)) {
 24 |     return {
 25 |       displayText: responseBuilder.buildErrorResponse('invalidPath', { path }),
 26 |       data: {
 27 |         success: false,
 28 |         error: 'Directory does not exist'
 29 |       }
 30 |     };
 31 |   }
 32 |   
 33 |   await reportProgress(onProgress, 33, 100, 'Checking document status...');
 34 |   
 35 |   const status = getWorkflowStatus(path);
 36 |   
 37 |   // Check if all files do not exist
 38 |   if (!status.requirements.exists && !status.design.exists && !status.tasks.exists) {
 39 |     await reportProgress(onProgress, 100, 100, 'Check completed');
 40 |     return {
 41 |       displayText: responseBuilder.buildErrorResponse('invalidPath', { 
 42 |         path,
 43 |         error: 'Project not initialized' 
 44 |       }),
 45 |       data: {
 46 |         success: false,
 47 |         error: 'Project not initialized'
 48 |       }
 49 |     };
 50 |   }
 51 |   
 52 |   const currentStage = getCurrentStage(status, path);
 53 |   // const stageStatus = getStageStatus(currentStage, status, path); // 未使用
 54 |   
 55 |   await reportProgress(onProgress, 66, 100, 'Analyzing document content...');
 56 |   
 57 |   // Analyze current stage
 58 |   const analysis = analyzeStage(path, currentStage, status);
 59 |   
 60 |   // Check if need to generate next document
 61 |   if (analysis.canProceed) {
 62 |     await generateNextDocument(path, currentStage);
 63 |   }
 64 |   
 65 |   await reportProgress(onProgress, 100, 100, 'Check completed');
 66 |   
 67 |   const progress = calculateWorkflowProgress(path, status);
 68 |   
 69 |   // Determine status type
 70 |   let statusType: string;
 71 |   let reason: string | undefined;
 72 |   
 73 |   if (currentStage === 'completed') {
 74 |     statusType = 'completed';
 75 |   } else if (!status[currentStage].exists) {
 76 |     statusType = 'not_edited';
 77 |     reason = `${currentStage === 'requirements' ? 'Requirements' : currentStage === 'design' ? 'Design' : 'Tasks'} document does not exist`;
 78 |   } else if (analysis.canProceed) {
 79 |     statusType = 'ready_to_confirm';
 80 |   } else if (analysis.needsConfirmation) {
 81 |     statusType = 'ready_to_confirm';
 82 |   } else {
 83 |     statusType = 'not_edited';
 84 |     reason = analysis.reason;
 85 |   }
 86 |   
 87 |   // If workflow is completed, get the first uncompleted task
 88 |   let firstTask = null;
 89 |   if (currentStage === 'completed') {
 90 |     const tasks = parseTasksFile(path);
 91 |     const task = getFirstUncompletedTask(tasks);
 92 |     if (task) {
 93 |       const tasksPath = join(path, 'tasks.md');
 94 |       const content = readFileSync(tasksPath, 'utf-8');
 95 |       firstTask = formatTaskForFullDisplay(task, content);
 96 |     }
 97 |   }
 98 |   
 99 |   return responseBuilder.buildCheckResponse(
100 |     currentStage,
101 |     progress,
102 |     { 
103 |       type: statusType, 
104 |       reason,
105 |       readyToConfirm: analysis.canProceed 
106 |     },
107 |     analysis,
108 |     path,
109 |     firstTask
110 |   );
111 | }
112 | 
113 | async function reportProgress(
114 |   onProgress: ((progress: number, total: number, message: string) => Promise<void>) | undefined,
115 |   progress: number,
116 |   total: number,
117 |   message: string
118 | ): Promise<void> {
119 |   if (onProgress) {
120 |     await onProgress(progress, total, message);
121 |   }
122 | }
```

--------------------------------------------------------------------------------
/src/features/confirm/confirmStage.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Confirm stage completion
  3 |  */
  4 | 
  5 | import { existsSync, readFileSync } from 'fs';
  6 | import { join } from 'path';
  7 | import { getWorkflowStatus, getStageName, getNextStage, getCurrentStage, getStageFileName } from '../shared/documentStatus.js';
  8 | import { updateStageConfirmation, isStageSkipped } from '../shared/confirmationStatus.js';
  9 | import { generateNextDocument } from '../check/generateNextDocument.js';
 10 | import { responseBuilder } from '../shared/responseBuilder.js';
 11 | import { WorkflowResult } from '../shared/mcpTypes.js';
 12 | import { parseTasksFile, getFirstUncompletedTask, formatTaskForFullDisplay } from '../shared/taskParser.js';
 13 | import { isDocumentEdited } from '../shared/documentAnalyzer.js';
 14 | import { calculateWorkflowProgress } from '../shared/progressCalculator.js';
 15 | 
 16 | export interface ConfirmOptions {
 17 |   path: string;
 18 | }
 19 | 
 20 | export async function confirmStage(options: ConfirmOptions): Promise<WorkflowResult> {
 21 |   const { path } = options;
 22 |   
 23 |   if (!existsSync(path)) {
 24 |     return {
 25 |       displayText: responseBuilder.buildErrorResponse('invalidPath', { path }),
 26 |       data: {
 27 |         success: false,
 28 |         error: 'Directory does not exist'
 29 |       }
 30 |     };
 31 |   }
 32 |   
 33 |   const status = getWorkflowStatus(path);
 34 |   const currentStage = getCurrentStage(status, path);
 35 |   
 36 |   // Check if all stages are completed
 37 |   if (currentStage === 'completed') {
 38 |     return {
 39 |       displayText: `✅ All stages completed!
 40 | 
 41 | Workflow completed, no need to confirm again.`,
 42 |       data: {
 43 |         success: false,
 44 |         reason: 'All stages completed'
 45 |       }
 46 |     };
 47 |   }
 48 |   
 49 |   const stageData = status[currentStage as keyof typeof status];
 50 |   
 51 |   // Check if document exists
 52 |   if (!stageData || !stageData.exists) {
 53 |     return {
 54 |       displayText: `⚠️ ${getStageName(currentStage)} does not exist
 55 | 
 56 | Please create ${getStageName(currentStage)} document before confirming.`,
 57 |       data: {
 58 |         success: false,
 59 |         reason: `${getStageName(currentStage)} does not exist`
 60 |       }
 61 |     };
 62 |   }
 63 |   
 64 |   // Check if already skipped
 65 |   if (isStageSkipped(path, currentStage)) {
 66 |     return {
 67 |       displayText: `⚠️ ${getStageName(currentStage)} already skipped
 68 | 
 69 | This stage has been skipped, no need to confirm.`,
 70 |       data: {
 71 |         success: false,
 72 |         reason: `${getStageName(currentStage)} already skipped`
 73 |       }
 74 |     };
 75 |   }
 76 |   
 77 |   // Check if document has been edited
 78 |   const fileName = getStageFileName(currentStage);
 79 |   const filePath = join(path, fileName);
 80 |   if (!isDocumentEdited(filePath)) {
 81 |     return {
 82 |       displayText: responseBuilder.buildErrorResponse('documentNotEdited', { 
 83 |         documentName: getStageName(currentStage)
 84 |       }),
 85 |       data: {
 86 |         success: false,
 87 |         error: 'Document not edited'
 88 |       }
 89 |     };
 90 |   }
 91 |   
 92 |   // Update confirmation status
 93 |   updateStageConfirmation(path, currentStage, true);
 94 |   
 95 |   // Get next stage
 96 |   const nextStage = getNextStage(currentStage);
 97 |   
 98 |   // Generate document for next stage
 99 |   if (nextStage !== 'completed') {
100 |     await generateNextDocument(path, currentStage);
101 |   }
102 |   
103 |   // If tasks stage, get first task details
104 |   let firstTaskContent = null;
105 |   if (currentStage === 'tasks' && nextStage === 'completed') {
106 |     const tasks = parseTasksFile(path);
107 |     const firstTask = getFirstUncompletedTask(tasks);
108 |     if (firstTask) {
109 |       const tasksPath = join(path, 'tasks.md');
110 |       const content = readFileSync(tasksPath, 'utf-8');
111 |       firstTaskContent = formatTaskForFullDisplay(firstTask, content);
112 |     }
113 |   }
114 |   
115 |   // Calculate progress after confirmation
116 |   const updatedStatus = getWorkflowStatus(path);
117 |   const progress = calculateWorkflowProgress(path, updatedStatus);
118 |   
119 |   return responseBuilder.buildConfirmResponse(currentStage, nextStage === 'completed' ? null : nextStage, path, firstTaskContent, progress);
120 | }
```

--------------------------------------------------------------------------------
/scripts/generateTypes.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env tsx
  2 | /**
  3 |  * Generate TypeScript type definitions from OpenAPI specification
  4 |  */
  5 | 
  6 | import * as fs from 'fs';
  7 | import * as path from 'path';
  8 | import * as yaml from 'js-yaml';
  9 | import { fileURLToPath } from 'url';
 10 | import { dirname } from 'path';
 11 | 
 12 | const __filename = fileURLToPath(import.meta.url);
 13 | const __dirname = dirname(__filename);
 14 | 
 15 | // Read OpenAPI specification
 16 | const specPath = path.join(__dirname, '../api/spec-workflow.openapi.yaml');
 17 | const spec = yaml.load(fs.readFileSync(specPath, 'utf8')) as any;
 18 | 
 19 | // Generate TypeScript types
 20 | function generateTypes(): string {
 21 |   const types: string[] = [];
 22 |   
 23 |   types.push('// Auto-generated type definitions - do not modify manually');
 24 |   types.push('// Generated from api/spec-workflow.openapi.yaml');
 25 |   types.push('');
 26 |   
 27 |   // Generate types for schemas
 28 |   for (const [schemaName, schema] of Object.entries(spec.components.schemas)) {
 29 |     types.push(generateSchemaType(schemaName, schema));
 30 |     types.push('');
 31 |   }
 32 |   
 33 |   // Generate extended types
 34 |   types.push('// Extended type definitions');
 35 |   types.push('export interface ErrorResponse {');
 36 |   types.push('  displayText: string;');
 37 |   types.push('  variables?: Record<string, any>;');
 38 |   types.push('}');
 39 |   types.push('');
 40 |   
 41 |   types.push('export interface ContentCheckRules {');
 42 |   types.push('  minLength?: number;');
 43 |   types.push('  requiredSections?: string[];');
 44 |   types.push('  optionalSections?: string[];');
 45 |   types.push('  minTasks?: number;');
 46 |   types.push('  taskFormat?: string;');
 47 |   types.push('  requiresEstimate?: boolean;');
 48 |   types.push('}');
 49 |   
 50 |   return types.join('\n');
 51 | }
 52 | 
 53 | function generateSchemaType(name: string, schema: any): string {
 54 |   const lines: string[] = [];
 55 |   
 56 |   lines.push(`export interface ${name} {`);
 57 |   
 58 |   if (schema.properties) {
 59 |     for (const [propName, prop] of Object.entries(schema.properties) as [string, any][]) {
 60 |       const required = schema.required?.includes(propName) || false;
 61 |       const optional = required ? '' : '?';
 62 |       const type = getTypeScriptType(prop);
 63 |       const comment = prop.description ? `  // ${prop.description}` : '';
 64 |       
 65 |       lines.push(`  ${propName}${optional}: ${type};${comment}`);
 66 |     }
 67 |   }
 68 |   
 69 |   // Handle oneOf
 70 |   if (schema.oneOf) {
 71 |     lines.push('  // oneOf:');
 72 |     schema.oneOf.forEach((item: any) => {
 73 |       if (item.$ref) {
 74 |         const refType = item.$ref.split('/').pop();
 75 |         lines.push(`  // - ${refType}`);
 76 |       }
 77 |     });
 78 |   }
 79 |   
 80 |   lines.push('}');
 81 |   
 82 |   return lines.join('\n');
 83 | }
 84 | 
 85 | function getTypeScriptType(prop: any): string {
 86 |   if (prop.$ref) {
 87 |     return prop.$ref.split('/').pop();
 88 |   }
 89 |   
 90 |   if (prop.oneOf) {
 91 |     const types = prop.oneOf.map((item: any) => {
 92 |       if (item.$ref) {
 93 |         return item.$ref.split('/').pop();
 94 |       }
 95 |       return getTypeScriptType(item);
 96 |     });
 97 |     return types.join(' | ');
 98 |   }
 99 |   
100 |   if (prop.enum) {
101 |     return prop.enum.map((v: any) => `'${v}'`).join(' | ');
102 |   }
103 |   
104 |   switch (prop.type) {
105 |     case 'string':
106 |       if (prop.const) {
107 |         return `'${prop.const}'`;
108 |       }
109 |       return 'string';
110 |     case 'number':
111 |     case 'integer':
112 |       return 'number';
113 |     case 'boolean':
114 |       return 'boolean';
115 |     case 'array':
116 |       if (prop.items) {
117 |         return `${getTypeScriptType(prop.items)}[]`;
118 |       }
119 |       return 'any[]';
120 |     case 'object':
121 |       if (prop.properties) {
122 |         const props = Object.entries(prop.properties)
123 |           .map(([k, v]: [string, any]) => `${k}: ${getTypeScriptType(v)}`)
124 |           .join('; ');
125 |         return `{ ${props} }`;
126 |       }
127 |       return 'Record<string, any>';
128 |     default:
129 |       return 'any';
130 |   }
131 | }
132 | 
133 | // Generate type file
134 | const types = generateTypes();
135 | const outputPath = path.join(__dirname, '../src/features/shared/openApiTypes.ts');
136 | fs.writeFileSync(outputPath, types, 'utf8');
137 | 
138 | console.log('✅ TypeScript types generated to:', outputPath);
```

--------------------------------------------------------------------------------
/src/features/skip/skipStage.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Skip current stage
  3 |  */
  4 | 
  5 | import { existsSync, writeFileSync } from 'fs';
  6 | import { join } from 'path';
  7 | import { getWorkflowStatus, getCurrentStage, getNextStage, getStageName, getStageFileName } from '../shared/documentStatus.js';
  8 | import { getSkippedTemplate, getDesignTemplate, getTasksTemplate } from '../shared/documentTemplates.js';
  9 | import { updateStageConfirmation, updateStageSkipped } from '../shared/confirmationStatus.js';
 10 | import { responseBuilder } from '../shared/responseBuilder.js';
 11 | import { WorkflowResult } from '../shared/mcpTypes.js';
 12 | import { extractDocumentInfo } from '../shared/documentUtils.js';
 13 | import { calculateWorkflowProgress } from '../shared/progressCalculator.js';
 14 | 
 15 | export interface SkipOptions {
 16 |   path: string;
 17 | }
 18 | 
 19 | export async function skipStage(options: SkipOptions): Promise<WorkflowResult> {
 20 |   const { path } = options;
 21 |   
 22 |   if (!existsSync(path)) {
 23 |     return {
 24 |       displayText: responseBuilder.buildErrorResponse('invalidPath', { path }),
 25 |       data: {
 26 |         success: false,
 27 |         error: 'Directory does not exist'
 28 |       }
 29 |     };
 30 |   }
 31 |   
 32 |   const status = getWorkflowStatus(path);
 33 |   const currentStage = getCurrentStage(status, path);
 34 |   
 35 |   if (currentStage === 'completed') {
 36 |     return {
 37 |       displayText: '✅ All stages completed, no need to skip',
 38 |       data: {
 39 |         success: false,
 40 |         reason: 'All stages completed'
 41 |       }
 42 |     };
 43 |   }
 44 |   
 45 |   // Get document information
 46 |   const documentInfo = extractDocumentInfo(join(path, 'requirements.md'));
 47 |   
 48 |   // Create document for skipped stage
 49 |   createSkippedDocument(path, currentStage, documentInfo.featureName);
 50 |   
 51 |   // Mark current stage as skipped
 52 |   updateStageSkipped(path, currentStage, true);
 53 |   // For tasks stage, don't mark as confirmed when skipping
 54 |   // since it's essential for development
 55 |   if (currentStage !== 'tasks') {
 56 |     updateStageConfirmation(path, currentStage, true);
 57 |   }
 58 |   
 59 |   // Generate next document (if needed)
 60 |   const nextStage = getNextStage(currentStage);
 61 |   
 62 |   if (nextStage !== 'completed') {
 63 |     createNextStageDocument(path, nextStage, documentInfo.featureName);
 64 |     // Initialize next stage confirmation status as unconfirmed
 65 |     updateStageConfirmation(path, nextStage, false);
 66 |   }
 67 |   
 68 |   // Calculate progress after skip
 69 |   const updatedStatus = getWorkflowStatus(path);
 70 |   const progress = calculateWorkflowProgress(path, updatedStatus);
 71 |   
 72 |   return responseBuilder.buildSkipResponse(currentStage, path, progress);
 73 | }
 74 | 
 75 | 
 76 | interface DocumentResult {
 77 |   created: boolean;
 78 |   fileName: string;
 79 |   message: string;
 80 | }
 81 | 
 82 | function createSkippedDocument(
 83 |   path: string,
 84 |   stage: string,
 85 |   featureName: string
 86 | ): DocumentResult {
 87 |   const fileName = getStageFileName(stage);
 88 |   const filePath = join(path, fileName);
 89 |   
 90 |   // If document already exists, don't overwrite
 91 |   if (existsSync(filePath)) {
 92 |     return {
 93 |       created: false,
 94 |       fileName,
 95 |       message: `${fileName} already exists, keeping original content`
 96 |     };
 97 |   }
 98 |   
 99 |   const content = getSkippedTemplate(getStageName(stage), featureName);
100 |   
101 |   try {
102 |     writeFileSync(filePath, content, 'utf-8');
103 |     return {
104 |       created: true,
105 |       fileName,
106 |       message: `Created skip marker document: ${fileName}`
107 |     };
108 |   } catch (error) {
109 |     return {
110 |       created: false,
111 |       fileName,
112 |       message: `Failed to create skip document: ${error}`
113 |     };
114 |   }
115 | }
116 | 
117 | function createNextStageDocument(
118 |   path: string,
119 |   stage: string,
120 |   featureName: string
121 | ): DocumentResult | null {
122 |   const fileName = getStageFileName(stage);
123 |   const filePath = join(path, fileName);
124 |   
125 |   if (existsSync(filePath)) {
126 |     return {
127 |       created: false,
128 |       fileName,
129 |       message: `${fileName} already exists`
130 |     };
131 |   }
132 |   
133 |   let content: string;
134 |   switch (stage) {
135 |     case 'design':
136 |       content = getDesignTemplate(featureName);
137 |       break;
138 |     case 'tasks':
139 |       content = getTasksTemplate(featureName);
140 |       break;
141 |     default:
142 |       return null;
143 |   }
144 |   
145 |   try {
146 |     writeFileSync(filePath, content, 'utf-8');
147 |     return {
148 |       created: true,
149 |       fileName,
150 |       message: `Created next stage document: ${fileName}`
151 |     };
152 |   } catch (error) {
153 |     return {
154 |       created: false,
155 |       fileName,
156 |       message: `Failed to create document: ${error}`
157 |     };
158 |   }
159 | }
```

--------------------------------------------------------------------------------
/scripts/validateOpenApi.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env tsx
  2 | /**
  3 |  * Validate if MCP responses conform to OpenAPI specification
  4 |  */
  5 | 
  6 | import { fileURLToPath } from 'url';
  7 | import { dirname, join } from 'path';
  8 | import * as fs from 'fs/promises';
  9 | import * as yaml from 'js-yaml';
 10 | 
 11 | const __filename = fileURLToPath(import.meta.url);
 12 | const __dirname = dirname(__filename);
 13 | 
 14 | // Load OpenAPI specification
 15 | async function loadOpenApiSpec() {
 16 |   const specPath = join(__dirname, '../api/spec-workflow.openapi.yaml');
 17 |   const specContent = await fs.readFile(specPath, 'utf-8');
 18 |   return yaml.load(specContent) as any;
 19 | }
 20 | 
 21 | // Manually validate response
 22 | function validateResponse(response: any, schemaName: string, spec: any): { valid: boolean; errors: string[] } {
 23 |   const errors: string[] = [];
 24 |   const schema = spec.components.schemas[schemaName];
 25 |   
 26 |   if (!schema) {
 27 |     return { valid: false, errors: [`Schema ${schemaName} not found`] };
 28 |   }
 29 |   
 30 |   // Check required fields
 31 |   if (schema.required) {
 32 |     for (const field of schema.required) {
 33 |       if (!(field in response)) {
 34 |         errors.push(`Missing required field: ${field}`);
 35 |       }
 36 |     }
 37 |   }
 38 |   
 39 |   // Check field types
 40 |   if (schema.properties) {
 41 |     for (const [field, fieldSchema] of Object.entries(schema.properties)) {
 42 |       if (field in response) {
 43 |         const value = response[field];
 44 |         const expectedType = (fieldSchema as any).type;
 45 |         
 46 |         if (expectedType) {
 47 |           const actualType = Array.isArray(value) ? 'array' : typeof value;
 48 |           
 49 |           if (expectedType === 'integer' && typeof value === 'number') {
 50 |             // integer and number are compatible
 51 |           } else if (expectedType !== actualType) {
 52 |             errors.push(`Field ${field}: expected ${expectedType}, got ${actualType}`);
 53 |           }
 54 |         }
 55 |         
 56 |         // Recursively check nested objects
 57 |         if ((fieldSchema as any).$ref) {
 58 |           const refSchemaName = (fieldSchema as any).$ref.split('/').pop();
 59 |           const nestedResult = validateResponse(value, refSchemaName, spec);
 60 |           errors.push(...nestedResult.errors.map(e => `${field}.${e}`));
 61 |         }
 62 |       }
 63 |     }
 64 |   }
 65 |   
 66 |   return { valid: errors.length === 0, errors };
 67 | }
 68 | 
 69 | // Test example responses
 70 | async function testResponses() {
 71 |   const spec = await loadOpenApiSpec();
 72 |   
 73 |   // Test response examples
 74 |   const testCases = [
 75 |     {
 76 |       name: 'InitResponse',
 77 |       response: {
 78 |         success: true,
 79 |         data: {
 80 |           path: '/test/path',
 81 |           featureName: 'Test Feature',
 82 |           nextAction: 'edit_requirements'
 83 |         },
 84 |         displayText: 'Initialization successful',
 85 |         resources: []
 86 |       }
 87 |     },
 88 |     {
 89 |       name: 'CheckResponse',
 90 |       response: {
 91 |         stage: 'requirements',
 92 |         progress: {
 93 |           overall: 30,
 94 |           requirements: 100,
 95 |           design: 0,
 96 |           tasks: 0
 97 |         },
 98 |         status: {
 99 |           type: 'ready_to_confirm',
100 |           readyToConfirm: true
101 |         },
102 |         displayText: 'Check passed'
103 |       }
104 |     },
105 |     {
106 |       name: 'SkipResponse',
107 |       response: {
108 |         stage: 'requirements',
109 |         skipped: true,
110 |         displayText: 'Skipped'
111 |       }
112 |     }
113 |   ];
114 |   
115 |   console.log('🧪 Validating OpenAPI response format\n');
116 |   
117 |   for (const testCase of testCases) {
118 |     console.log(`📝 Testing ${testCase.name}:`);
119 |     const result = validateResponse(testCase.response, testCase.name, spec);
120 |     
121 |     if (result.valid) {
122 |       console.log('   ✅ Validation passed');
123 |     } else {
124 |       console.log('   ❌ Validation failed:');
125 |       result.errors.forEach(error => {
126 |         console.log(`      - ${error}`);
127 |       });
128 |     }
129 |     console.log();
130 |   }
131 |   
132 |   // Check Progress definition
133 |   console.log('📊 Progress Schema definition:');
134 |   const progressSchema = spec.components.schemas.Progress;
135 |   console.log('Required fields:', progressSchema.required);
136 |   console.log('Properties:', Object.keys(progressSchema.properties));
137 |   console.log();
138 |   
139 |   // Test Progress
140 |   const progressTest = {
141 |     overall: 30,
142 |     requirements: 100,
143 |     design: 0,
144 |     tasks: 0
145 |   };
146 |   
147 |   const progressResult = validateResponse(progressTest, 'Progress', spec);
148 |   console.log('Progress validation:', progressResult.valid ? '✅ Passed' : '❌ Failed');
149 |   if (!progressResult.valid) {
150 |     progressResult.errors.forEach(error => {
151 |       console.log(`   - ${error}`);
152 |     });
153 |   }
154 | }
155 | 
156 | if (import.meta.url === `file://${process.argv[1]}`) {
157 |   testResponses().catch(console.error);
158 | }
```

--------------------------------------------------------------------------------
/src/features/init/initWorkflow.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Initialize workflow functionality
  3 |  */
  4 | 
  5 | import { existsSync, mkdirSync } from 'fs';
  6 | import { join } from 'path';
  7 | import { getWorkflowStatus, getCurrentStage } from '../shared/documentStatus.js';
  8 | import { calculateWorkflowProgress } from '../shared/progressCalculator.js';
  9 | import { createRequirementsDocument } from './createRequirementsDoc.js';
 10 | import { updateStageConfirmation } from '../shared/confirmationStatus.js';
 11 | import { responseBuilder } from '../shared/responseBuilder.js';
 12 | import { WorkflowResult } from '../shared/mcpTypes.js';
 13 | 
 14 | export interface InitOptions {
 15 |   path: string;
 16 |   featureName: string;
 17 |   introduction: string;
 18 |   onProgress?: (progress: number, total: number, message: string) => Promise<void>;
 19 | }
 20 | 
 21 | export async function initWorkflow(options: InitOptions): Promise<WorkflowResult> {
 22 |   const { path, featureName, introduction, onProgress } = options;
 23 |   
 24 |   try {
 25 |     await reportProgress(onProgress, 0, 100, 'Starting initialization...');
 26 |     
 27 |     // Create directory
 28 |     if (!existsSync(path)) {
 29 |       mkdirSync(path, { recursive: true });
 30 |     }
 31 |     
 32 |     await reportProgress(onProgress, 50, 100, 'Checking project status...');
 33 |     
 34 |     // Comprehensively check if project already exists
 35 |     const requirementsPath = join(path, 'requirements.md');
 36 |     const designPath = join(path, 'design.md');
 37 |     const tasksPath = join(path, 'tasks.md');
 38 |     const confirmationsPath = join(path, '.workflow-confirmations.json');
 39 |     
 40 |     // If any workflow-related files exist, consider the project already exists
 41 |     const projectExists = existsSync(requirementsPath) || 
 42 |                          existsSync(designPath) || 
 43 |                          existsSync(tasksPath) || 
 44 |                          existsSync(confirmationsPath);
 45 |     
 46 |     if (projectExists) {
 47 |       await reportProgress(onProgress, 100, 100, 'Found existing project');
 48 |       
 49 |       const status = getWorkflowStatus(path);
 50 |       const currentStage = getCurrentStage(status, path);
 51 |       const progress = calculateWorkflowProgress(path, status);
 52 |       
 53 |       const enhancedStatus = {
 54 |         ...status,
 55 |         design: { ...status.design, exists: existsSync(designPath) },
 56 |         tasks: { ...status.tasks, exists: existsSync(tasksPath) }
 57 |       };
 58 |       
 59 |       // Build detailed existing reason
 60 |       const existingFiles = [];
 61 |       if (existsSync(requirementsPath)) existingFiles.push('Requirements document');
 62 |       if (existsSync(designPath)) existingFiles.push('Design document');
 63 |       if (existsSync(tasksPath)) existingFiles.push('Task list');
 64 |       if (existsSync(confirmationsPath)) existingFiles.push('Workflow status');
 65 |       
 66 |       // Use responseBuilder to build error response
 67 |       return {
 68 |         displayText: responseBuilder.buildErrorResponse('alreadyInitialized', { 
 69 |           path,
 70 |           existingFiles: existingFiles.join(', ')
 71 |         }),
 72 |         data: {
 73 |           success: false,
 74 |           error: 'PROJECT_ALREADY_EXISTS',
 75 |           existingFiles: existingFiles,
 76 |           currentStage: currentStage,
 77 |           progress: progress
 78 |         }
 79 |       };
 80 |     }
 81 |     
 82 |     // Generate requirements document
 83 |     const result = createRequirementsDocument(path, featureName, introduction);
 84 |     
 85 |     if (!result.generated) {
 86 |       return {
 87 |         displayText: responseBuilder.buildErrorResponse('invalidPath', { path }),
 88 |         data: {
 89 |           success: false,
 90 |           error: 'Failed to create requirements document',
 91 |           details: result
 92 |         }
 93 |       };
 94 |     }
 95 |     
 96 |     // Initialize status file, mark requirements stage as unconfirmed
 97 |     updateStageConfirmation(path, 'requirements', false);
 98 |     updateStageConfirmation(path, 'design', false);
 99 |     updateStageConfirmation(path, 'tasks', false);
100 |     
101 |     await reportProgress(onProgress, 100, 100, 'Initialization completed!');
102 |     
103 |     // Use responseBuilder to build success response
104 |     return responseBuilder.buildInitResponse(path, featureName);
105 |     
106 |   } catch (error) {
107 |     return {
108 |       displayText: responseBuilder.buildErrorResponse('invalidPath', { 
109 |         path,
110 |         error: String(error) 
111 |       }),
112 |       data: {
113 |         success: false,
114 |         error: `Initialization failed: ${error}`
115 |       }
116 |     };
117 |   }
118 | }
119 | 
120 | async function reportProgress(
121 |   onProgress: ((progress: number, total: number, message: string) => Promise<void>) | undefined,
122 |   progress: number,
123 |   total: number,
124 |   message: string
125 | ): Promise<void> {
126 |   if (onProgress) {
127 |     await onProgress(progress, total, message);
128 |   }
129 | }
```

--------------------------------------------------------------------------------
/src/features/check/analyzeStage.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Analyze workflow stage
  3 |  */
  4 | 
  5 | import { join } from 'path';
  6 | import { WorkflowStatus, WorkflowStage, getStageFileName, getStageName } from '../shared/documentStatus.js';
  7 | import { isStageConfirmed, isStageSkipped, ConfirmationStatus, SkipStatus } from '../shared/confirmationStatus.js';
  8 | import { openApiLoader } from '../shared/openApiLoader.js';
  9 | import { isDocumentEdited } from '../shared/documentAnalyzer.js';
 10 | import { isObject, hasProperty } from '../shared/typeGuards.js';
 11 | 
 12 | export interface StageAnalysis {
 13 |   canProceed: boolean;
 14 |   needsConfirmation: boolean;
 15 |   reason?: string;
 16 |   suggestions: string[];
 17 |   guide?: unknown; // Writing guide provided when document is not edited
 18 | }
 19 | 
 20 | export interface StageStatus {
 21 |   exists: boolean;
 22 |   confirmed: boolean;
 23 |   skipped: boolean;
 24 |   displayStatus: string;
 25 | }
 26 | 
 27 | export function analyzeStage(
 28 |   path: string,
 29 |   stage: WorkflowStage,
 30 |   status: WorkflowStatus
 31 | ): StageAnalysis {
 32 |   if (stage === 'completed') {
 33 |     return {
 34 |       canProceed: false,
 35 |       needsConfirmation: false,
 36 |       reason: 'All stages completed',
 37 |       suggestions: []
 38 |     };
 39 |   }
 40 |   
 41 |   const stageData = status[stage as keyof WorkflowStatus];
 42 |   // Type guard to handle 'completed' stage
 43 |   const isCompletedStage = (s: WorkflowStage): s is 'completed' => s === 'completed';
 44 |   const confirmed = isCompletedStage(stage) ? false : isStageConfirmed(path, stage as keyof ConfirmationStatus);
 45 |   const skipped = isCompletedStage(stage) ? false : isStageSkipped(path, stage as keyof SkipStatus);
 46 |   
 47 |   // If stage is skipped or confirmed, can proceed
 48 |   if (confirmed || skipped) {
 49 |     return {
 50 |       canProceed: true,
 51 |       needsConfirmation: false,
 52 |       suggestions: [`${getStageName(stage)} completed, can proceed to next stage`]
 53 |     };
 54 |   }
 55 |   
 56 |   // Check if document exists
 57 |   if (!stageData || !stageData.exists) {
 58 |     return {
 59 |       canProceed: false,
 60 |       needsConfirmation: false,
 61 |       reason: `${getStageName(stage)} does not exist`,
 62 |       suggestions: [`Create ${getStageName(stage)}`],
 63 |       guide: getStageGuide(stage)
 64 |     };
 65 |   }
 66 |   
 67 |   // Check if document has been edited
 68 |   const fileName = getStageFileName(stage);
 69 |   const filePath = join(path, fileName);
 70 |   const edited = isDocumentEdited(filePath);
 71 |   
 72 |   if (!edited) {
 73 |     // Document exists but not edited
 74 |     return {
 75 |       canProceed: false,
 76 |       needsConfirmation: false,
 77 |       reason: `${getStageName(stage)} not edited yet (still contains template markers)`,
 78 |       suggestions: [
 79 |         `Please edit ${fileName} and remove all <template-*> markers`,
 80 |         'Fill in actual content before using check operation'
 81 |       ],
 82 |       guide: getStageGuide(stage)
 83 |     };
 84 |   }
 85 |   
 86 |   // Document edited but not confirmed
 87 |   return {
 88 |     canProceed: false,
 89 |     needsConfirmation: true,
 90 |     reason: `${getStageName(stage)} edited but not confirmed yet`,
 91 |     suggestions: ['Please use confirm operation to confirm this stage is complete'],
 92 |     guide: getStageGuide(stage)
 93 |   };
 94 | }
 95 | 
 96 | export function getStageStatus(
 97 |   stage: string,
 98 |   status: WorkflowStatus,
 99 |   path: string
100 | ): StageStatus {
101 |   const stageData = status[stage as keyof WorkflowStatus];
102 |   const exists = stageData?.exists || false;
103 |   const confirmed = exists && stage !== 'completed' ? isStageConfirmed(path, stage as keyof ConfirmationStatus) : false;
104 |   const skipped = exists && stage !== 'completed' ? isStageSkipped(path, stage as keyof SkipStatus) : false;
105 |   
106 |   const globalConfig = openApiLoader.getGlobalConfig();
107 |   const statusTextConfig = isObject(globalConfig) && hasProperty(globalConfig, 'status_text') && isObject(globalConfig.status_text) ? globalConfig.status_text : {};
108 |   const statusText = {
109 |     not_created: typeof statusTextConfig.not_created === 'string' ? statusTextConfig.not_created : 'Not created',
110 |     not_confirmed: typeof statusTextConfig.not_confirmed === 'string' ? statusTextConfig.not_confirmed : 'Pending confirmation',
111 |     completed: typeof statusTextConfig.completed === 'string' ? statusTextConfig.completed : 'Completed',
112 |     skipped: typeof statusTextConfig.skipped === 'string' ? statusTextConfig.skipped : 'Skipped'
113 |   };
114 |   
115 |   let displayStatus = statusText.not_created;
116 |   if (exists) {
117 |     if (skipped) {
118 |       displayStatus = statusText.skipped;
119 |     } else if (confirmed) {
120 |       displayStatus = statusText.completed;
121 |     } else {
122 |       displayStatus = statusText.not_confirmed;
123 |     }
124 |   }
125 |   
126 |   return {
127 |     exists,
128 |     confirmed,
129 |     skipped,
130 |     displayStatus
131 |   };
132 | }
133 | 
134 | 
135 | function getStageGuide(stage: WorkflowStage): unknown {
136 |   const guideMap: Record<WorkflowStage, string> = {
137 |     requirements: 'requirements-guide',
138 |     design: 'design-guide',
139 |     tasks: 'tasks-guide',
140 |     completed: ''
141 |   };
142 |   
143 |   const guideId = guideMap[stage];
144 |   if (!guideId) return null;
145 |   
146 |   // Get resource from OpenAPI - already in MCP format
147 |   const resource = openApiLoader.getSharedResource(guideId);
148 |   return resource || null;
149 | }
```

--------------------------------------------------------------------------------
/src/features/shared/taskGuidanceTemplate.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Task guidance template extractor
  3 |  * Reads task completion guidance text templates from OpenAPI specification
  4 |  */
  5 | 
  6 | import { openApiLoader, OpenApiLoader } from './openApiLoader.js';
  7 | 
  8 | export class TaskGuidanceExtractor {
  9 |   private static _template: ReturnType<typeof openApiLoader.getTaskGuidanceTemplate> | undefined;
 10 |   
 11 |   private static get template() {
 12 |     if (!this._template) {
 13 |       // Lazy loading to ensure OpenAPI spec is loaded
 14 |       openApiLoader.loadSpec();
 15 |       this._template = openApiLoader.getTaskGuidanceTemplate();
 16 |     }
 17 |     return this._template;
 18 |   }
 19 |   
 20 |   /**
 21 |    * Build task guidance text
 22 |    * Read templates from OpenAPI spec and assemble them
 23 |    */
 24 |   static buildGuidanceText(
 25 |     nextTaskContent: string,
 26 |     firstSubtask: string,
 27 |     taskNumber?: string,
 28 |     isFirstTask: boolean = false
 29 |   ): string {
 30 |     const template = this.template;
 31 |     if (!template) {
 32 |       throw new Error('Failed to load task guidance template from OpenAPI specification');
 33 |     }
 34 |     
 35 |     const parts: string[] = [];
 36 |     
 37 |     // Add separator line
 38 |     parts.push(template.separator);
 39 |     parts.push('');
 40 |     
 41 |     // Add task header
 42 |     parts.push(template.header);
 43 |     parts.push(nextTaskContent);
 44 |     parts.push('');
 45 |     
 46 |     // Add model instructions
 47 |     parts.push(template.instructions.prefix);
 48 |     const taskFocusText = OpenApiLoader.replaceVariables(template.instructions.taskFocus, { firstSubtask });
 49 |     parts.push(taskFocusText);
 50 |     
 51 |     parts.push('');
 52 |     parts.push(template.instructions.progressTracking);
 53 |     parts.push(template.instructions.workflow);
 54 |     parts.push('');
 55 |     
 56 |     // Add model prompt based on scenario
 57 |     let prompt: string;
 58 |     if (isFirstTask) {
 59 |       // Replace firstSubtask placeholder in firstTask prompt
 60 |       prompt = OpenApiLoader.replaceVariables(template.prompts.firstTask, { firstSubtask });
 61 |     } else if (taskNumber) {
 62 |       // Determine if it's a new task or continuation
 63 |       if (taskNumber.includes('.')) {
 64 |         // Subtask, use continuation prompt
 65 |         prompt = OpenApiLoader.replaceVariables(template.prompts.continueTask, { taskNumber, firstSubtask });
 66 |       } else {
 67 |         // Main task, use new task prompt
 68 |         prompt = OpenApiLoader.replaceVariables(template.prompts.nextTask, { taskNumber, firstSubtask });
 69 |       }
 70 |     } else {
 71 |       // Batch completion scenario, no specific task number
 72 |       prompt = OpenApiLoader.replaceVariables(template.prompts.batchContinue, { firstSubtask });
 73 |     }
 74 |     
 75 |     parts.push(prompt);
 76 |     
 77 |     return parts.join('\n');
 78 |   }
 79 |   
 80 |   /**
 81 |    * Extract the first uncompleted task with its context
 82 |    */
 83 |   static extractFirstSubtask(taskContent: string): string {
 84 |     const taskLines = taskContent.split('\n');
 85 |     let firstSubtaskFound = false;
 86 |     let firstSubtaskLines: string[] = [];
 87 |     let currentIndent = '';
 88 | 
 89 |     for (let i = 0; i < taskLines.length; i++) {
 90 |       const line = taskLines[i];
 91 | 
 92 |       // 忽略空行(但在收集过程中保留)
 93 |       if (!line.trim()) {
 94 |         if (firstSubtaskFound) {
 95 |           firstSubtaskLines.push(line);
 96 |         }
 97 |         continue;
 98 |       }
 99 | 
100 |       // 寻找第一个包含 [ ] 的行(未完成任务)
101 |       if (line.includes('[ ]') && !firstSubtaskFound) {
102 |         // 提取任务号验证这是一个子任务(包含点号)
103 |         const taskMatch = line.match(/(\d+(?:\.\d+)+)\./);
104 |         if (taskMatch) {
105 |           firstSubtaskFound = true;
106 |           firstSubtaskLines.push(line);
107 |           currentIndent = line.match(/^(\s*)/)?.[1] || '';
108 |           continue;
109 |         }
110 |       }
111 | 
112 |       // 如果已经找到第一个子任务,继续收集其详细内容
113 |       if (firstSubtaskFound) {
114 |         const lineIndent = line.match(/^(\s*)/)?.[1] || '';
115 | 
116 |         // 如果遇到同级或更高级的任务,停止收集
117 |         if (line.includes('[ ]') && lineIndent.length <= currentIndent.length) {
118 |           break;
119 |         }
120 | 
121 |         // 如果是更深层次的缩进内容,继续收集
122 |         if (lineIndent.length > currentIndent.length || line.trim().startsWith('-') || line.trim().startsWith('*')) {
123 |           firstSubtaskLines.push(line);
124 |         } else {
125 |           // 遇到非缩进内容,停止收集
126 |           break;
127 |         }
128 |       }
129 |     }
130 | 
131 |     // 如果找到了第一个子任务,返回其完整内容
132 |     if (firstSubtaskLines.length > 0) {
133 |       return firstSubtaskLines.join('\n').trim();
134 |     }
135 | 
136 |     // 如果没有找到子任务,尝试找第一个未完成的任务
137 |     for (const line of taskLines) {
138 |       if (!line.trim()) continue;
139 | 
140 |       if (line.includes('[ ]')) {
141 |         const taskMatch = line.match(/(\d+(?:\.\d+)*)\.\s*(.+)/);
142 |         if (taskMatch) {
143 |           const taskNumber = taskMatch[1];
144 |           const taskDesc = taskMatch[2].replace(/\*\*|\*/g, '').trim();
145 |           return `${taskNumber}. ${taskDesc}`;
146 |         }
147 |         return line.replace(/[-[\]\s]/g, '').replace(/\*\*|\*/g, '').trim();
148 |       }
149 |     }
150 | 
151 |     return 'Next task';
152 |   }
153 |   
154 |   /**
155 |    * Get completion message
156 |    */
157 |   static getCompletionMessage(type: 'taskCompleted' | 'allCompleted' | 'alreadyCompleted' | 'batchSucceeded' | 'batchCompleted', taskNumber?: string): string {
158 |     const template = this.template;
159 |     if (!template) {
160 |       throw new Error('Failed to load task guidance template from OpenAPI specification');
161 |     }
162 |     
163 |     const message = template.completionMessages[type];
164 |     if (taskNumber && message.includes('${taskNumber}')) {
165 |       return OpenApiLoader.replaceVariables(message, { taskNumber });
166 |     }
167 |     return message;
168 |   }
169 | }
```

--------------------------------------------------------------------------------
/src/features/shared/openApiLoader.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as yaml from 'js-yaml';
  2 | import * as fs from 'fs';
  3 | import * as path from 'path';
  4 | import { fileURLToPath } from 'url';
  5 | import { dirname } from 'path';
  6 | import { isObject } from './typeGuards.js';
  7 | 
  8 | const __filename = fileURLToPath(import.meta.url);
  9 | const __dirname = dirname(__filename);
 10 | 
 11 | // OpenAPI specification type definitions
 12 | export interface OpenApiSpec {
 13 |   paths: {
 14 |     '/spec': {
 15 |       post: {
 16 |         responses: {
 17 |           '200': {
 18 |             content: {
 19 |               'application/json': {
 20 |                 schema: {
 21 |                   $ref: string;
 22 |                 };
 23 |               };
 24 |             };
 25 |           };
 26 |         };
 27 |       };
 28 |     };
 29 |   };
 30 |   components: {
 31 |     schemas: Record<string, unknown>;
 32 |   };
 33 |   'x-error-responses': Record<string, {
 34 |     displayText: string;
 35 |   }>;
 36 |   'x-shared-resources': Record<string, {
 37 |     uri: string;
 38 |     title?: string;
 39 |     mimeType: string;
 40 |     text?: string;
 41 |   }>;
 42 |   'x-global-config': unknown;
 43 |   'x-document-templates': Record<string, unknown>;
 44 |   'x-task-guidance-template'?: {
 45 |     separator: string;
 46 |     header: string;
 47 |     instructions: {
 48 |       prefix: string;
 49 |       taskFocus: string;
 50 |       progressTracking: string;
 51 |       workflow: string;
 52 |     };
 53 |     prompts: {
 54 |       firstTask: string;
 55 |       nextTask: string;
 56 |       continueTask: string;
 57 |       batchContinue: string;
 58 |     };
 59 |     completionMessages: {
 60 |       taskCompleted: string;
 61 |       allCompleted: string;
 62 |       alreadyCompleted: string;
 63 |       batchSucceeded: string;
 64 |       batchCompleted: string;
 65 |     };
 66 |   };
 67 | }
 68 | 
 69 | // Singleton pattern for loading OpenAPI specification
 70 | export class OpenApiLoader {
 71 |   private static instance: OpenApiLoader;
 72 |   private spec: OpenApiSpec | null = null;
 73 |   private examples: Map<string, unknown[]> = new Map();
 74 | 
 75 |   private constructor() {}
 76 | 
 77 |   static getInstance(): OpenApiLoader {
 78 |     if (!OpenApiLoader.instance) {
 79 |       OpenApiLoader.instance = new OpenApiLoader();
 80 |     }
 81 |     return OpenApiLoader.instance;
 82 |   }
 83 | 
 84 |   // Load OpenAPI specification
 85 |   loadSpec(): OpenApiSpec {
 86 |     if (this.spec) {
 87 |       return this.spec;
 88 |     }
 89 | 
 90 |     const specPath = path.join(__dirname, '../../../api/spec-workflow.openapi.yaml');
 91 |     const specContent = fs.readFileSync(specPath, 'utf8');
 92 |     this.spec = yaml.load(specContent) as OpenApiSpec;
 93 | 
 94 |     // Parse and cache all examples
 95 |     this.cacheExamples();
 96 | 
 97 |     return this.spec;
 98 |   }
 99 | 
100 |   // Cache all response examples
101 |   private cacheExamples(): void {
102 |     if (!this.spec) return;
103 | 
104 |     const schemas = this.spec.components.schemas;
105 |     for (const [schemaName, schema] of Object.entries(schemas)) {
106 |       if (!isObject(schema)) continue;
107 |       // Support standard OpenAPI 3.1.0 examples field
108 |       if ('examples' in schema && Array.isArray(schema.examples)) {
109 |         this.examples.set(schemaName, schema.examples);
110 |       }
111 |       // Maintain backward compatibility with custom x-examples field
112 |       else if ('x-examples' in schema && Array.isArray(schema['x-examples'])) {
113 |         this.examples.set(schemaName, schema['x-examples']);
114 |       }
115 |     }
116 |   }
117 | 
118 |   // Get response example
119 |   getResponseExample(responseType: string, criteria?: Record<string, unknown>): unknown {
120 |     const examples = this.examples.get(responseType);
121 |     if (!examples || examples.length === 0) {
122 |       return null;
123 |     }
124 | 
125 |     // If no filter criteria, return the first example
126 |     if (!criteria) {
127 |       return examples[0];
128 |     }
129 | 
130 |     // Filter examples by criteria
131 |     for (const example of examples) {
132 |       let matches = true;
133 |       for (const [key, value] of Object.entries(criteria)) {
134 |         if (this.getNestedValue(example, key) !== value) {
135 |           matches = false;
136 |           break;
137 |         }
138 |       }
139 |       if (matches) {
140 |         return example;
141 |       }
142 |     }
143 | 
144 |     // No match found, return the first one
145 |     return examples[0];
146 |   }
147 | 
148 |   // Get error response template
149 |   getErrorResponse(errorType: string): string | null {
150 |     if (!this.spec || !this.spec['x-error-responses']) {
151 |       return null;
152 |     }
153 |     
154 |     const errorResponse = this.spec['x-error-responses'][errorType];
155 |     return errorResponse?.displayText || null;
156 |   }
157 | 
158 | 
159 |   // Get progress calculation rules
160 |   getProgressRules(): unknown {
161 |     if (!this.spec) return null;
162 | 
163 |     const progressSchema = this.spec.components.schemas.Progress;
164 |     if (isObject(progressSchema) && 'x-progress-rules' in progressSchema) {
165 |       return progressSchema['x-progress-rules'];
166 |     }
167 |     return null;
168 |   }
169 | 
170 |   // Utility function: get nested object value
171 |   private getNestedValue(obj: unknown, path: string): unknown {
172 |     const keys = path.split('.');
173 |     let current = obj;
174 |     
175 |     for (const key of keys) {
176 |       if (isObject(current) && key in current) {
177 |         current = current[key];
178 |       } else {
179 |         return undefined;
180 |       }
181 |     }
182 |     
183 |     return current;
184 |   }
185 | 
186 |   // Replace template variables
187 |   static replaceVariables(template: string, variables: Record<string, unknown>): string {
188 |     let result = template;
189 |     
190 |     for (const [key, value] of Object.entries(variables)) {
191 |       const regex = new RegExp(`\\$\\{${key}\\}`, 'g');
192 |       result = result.replace(regex, String(value));
193 |     }
194 |     
195 |     return result;
196 |   }
197 | 
198 |   // Get shared resource - directly return MCP format
199 |   getSharedResource(resourceId: string): { uri: string; title?: string; mimeType: string; text?: string } | null {
200 |     if (!this.spec || !this.spec['x-shared-resources']) {
201 |       return null;
202 |     }
203 |     
204 |     return this.spec['x-shared-resources'][resourceId] || null;
205 |   }
206 | 
207 |   // Get global configuration
208 |   getGlobalConfig(): unknown {
209 |     if (!this.spec) return {};
210 |     return this.spec['x-global-config'] || {};
211 |   }
212 | 
213 |   // Get document template
214 |   getDocumentTemplate(templateType: string): unknown {
215 |     if (!this.spec) return null;
216 |     return this.spec['x-document-templates']?.[templateType] || null;
217 |   }
218 | 
219 |   // Resolve resource list - no conversion needed, use MCP format directly
220 |   resolveResources(resources?: Array<unknown>): Array<{ uri: string; title?: string; mimeType: string; text?: string }> | undefined {
221 |     if (!resources || resources.length === 0) {
222 |       return undefined;
223 |     }
224 | 
225 |     const resolved: Array<{ uri: string; title?: string; mimeType: string; text?: string }> = [];
226 |     
227 |     for (const resource of resources) {
228 |       if (isObject(resource) && 'ref' in resource && typeof resource.ref === 'string') {
229 |         // Get from shared resources - already in MCP format
230 |         const sharedResource = this.getSharedResource(resource.ref);
231 |         if (sharedResource) {
232 |           resolved.push(sharedResource);
233 |         }
234 |       }
235 |     }
236 | 
237 |     return resolved.length > 0 ? resolved : undefined;
238 |   }
239 | 
240 |   // Get task guidance template
241 |   getTaskGuidanceTemplate(): OpenApiSpec['x-task-guidance-template'] | null {
242 |     if (!this.spec) return null;
243 |     return this.spec['x-task-guidance-template'] || null;
244 |   }
245 | 
246 |   // Debug method: get cached examples count
247 |   getExamplesCount(responseType: string): number {
248 |     return this.examples.get(responseType)?.length || 0;
249 |   }
250 | }
251 | 
252 | export const openApiLoader = OpenApiLoader.getInstance();
253 | 
```

--------------------------------------------------------------------------------
/src/features/shared/taskParser.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Parse tasks from tasks.md file
  3 |  */
  4 | 
  5 | import { readFileSync } from 'fs';
  6 | import { join } from 'path';
  7 | 
  8 | export interface Task {
  9 |   number: string;
 10 |   description: string;
 11 |   checked: boolean;
 12 |   subtasks?: Task[];
 13 |   isVirtual?: boolean; // 标识是否为虚拟创建的任务
 14 | }
 15 | 
 16 | export function parseTasksFile(path: string): Task[] {
 17 |   try {
 18 |     const tasksPath = join(path, 'tasks.md');
 19 |     const content = readFileSync(tasksPath, 'utf-8');
 20 |     
 21 |     // Remove template marker blocks
 22 |     const cleanContent = content
 23 |       .replace(/<!--\s*SPEC-MARKER[\s\S]*?-->/g, '') // Compatible with old format
 24 |       .replace(/<template-tasks>[\s\S]*?<\/template-tasks>/g, '') // Match actual task template markers
 25 |       .trim();
 26 |     
 27 |     if (!cleanContent) {
 28 |       return [];
 29 |     }
 30 |     
 31 |     return parseTasksFromContent(cleanContent);
 32 |   } catch {
 33 |     return [];
 34 |   }
 35 | }
 36 | 
 37 | export function parseTasksFromContent(content: string): Task[] {
 38 |   const lines = content.split('\n');
 39 |   const allTasks: Task[] = [];
 40 |   
 41 |   // Phase 1: Collect all tasks with checkboxes
 42 |   for (let i = 0; i < lines.length; i++) {
 43 |     const line = lines[i];
 44 |     
 45 |     // Find checkbox pattern
 46 |     const checkboxMatch = line.match(/\[([xX ])\]/);
 47 |     if (!checkboxMatch) continue;
 48 |     
 49 |     // Extract task number (flexible matching)
 50 |     const numberMatch = line.match(/(\d+(?:\.\d+)*)/);
 51 |     if (!numberMatch) continue;
 52 |     
 53 |     const taskNumber = numberMatch[1];
 54 |     const isChecked = checkboxMatch[1].toLowerCase() === 'x';
 55 |     
 56 |     // Extract description (remove task number and checkbox)
 57 |     let description = line
 58 |       .replace(/\[([xX ])\]/, '')  // Remove checkbox
 59 |       .replace(/(\d+(?:\.\d+)*)\s*[.:\-)]?/, '') // Remove task number
 60 |       .replace(/^[\s\-*]+/, '')  // Remove leading symbols
 61 |       .trim();
 62 |     
 63 |     // If description is empty, try to get from next line
 64 |     if (!description && i + 1 < lines.length) {
 65 |       const nextLine = lines[i + 1].trim();
 66 |       if (nextLine && !nextLine.match(/\[([xX ])\]/) && !nextLine.match(/^#/)) {
 67 |         description = nextLine;
 68 |         i++; // Skip next line
 69 |       }
 70 |     }
 71 |     
 72 |     if (!description) continue;
 73 |     
 74 |     allTasks.push({
 75 |       number: taskNumber,
 76 |       description: description,
 77 |       checked: isChecked
 78 |     });
 79 |   }
 80 |   
 81 |   // Phase 2: Build hierarchy structure
 82 |   const taskMap = new Map<string, Task>();
 83 |   const rootTasks: Task[] = [];
 84 |   
 85 |   // Infer main tasks from task numbers
 86 |   for (const task of allTasks) {
 87 |     if (!task.number.includes('.')) {
 88 |       // Top-level task
 89 |       taskMap.set(task.number, task);
 90 |       rootTasks.push(task);
 91 |     }
 92 |   }
 93 |   
 94 |   // Process subtasks
 95 |   for (const task of allTasks) {
 96 |     if (task.number.includes('.')) {
 97 |       const parts = task.number.split('.');
 98 |       const parentNumber = parts[0];
 99 |       
100 |       // If main task doesn't exist, create virtual parent task
101 |       if (!taskMap.has(parentNumber)) {
102 |         // Try to find better title from document
103 |         const betterTitle = findMainTaskTitle(lines, parentNumber);
104 |         const virtualParent: Task = {
105 |           number: parentNumber,
106 |           description: betterTitle || `Task Group ${parentNumber}`,
107 |           checked: false,
108 |           subtasks: [],
109 |           isVirtual: true // 标记为虚拟任务
110 |         };
111 |         taskMap.set(parentNumber, virtualParent);
112 |         rootTasks.push(virtualParent);
113 |       }
114 |       
115 |       // Add subtask to main task
116 |       const parent = taskMap.get(parentNumber)!;
117 |       if (!parent.subtasks) {
118 |         parent.subtasks = [];
119 |       }
120 |       parent.subtasks.push(task);
121 |     }
122 |   }
123 |   
124 |   // Update main task completion status (only when all subtasks are completed)
125 |   for (const task of rootTasks) {
126 |     if (task.subtasks && task.subtasks.length > 0) {
127 |       task.checked = task.subtasks.every(st => st.checked);
128 |     }
129 |   }
130 |   
131 |   // Sort by task number
132 |   rootTasks.sort((a, b) => {
133 |     const numA = parseInt(a.number);
134 |     const numB = parseInt(b.number);
135 |     return numA - numB;
136 |   });
137 |   
138 |   // Sort subtasks
139 |   for (const task of rootTasks) {
140 |     if (task.subtasks) {
141 |       task.subtasks.sort((a, b) => {
142 |         const partsA = a.number.split('.').map(n => parseInt(n));
143 |         const partsB = b.number.split('.').map(n => parseInt(n));
144 |         for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
145 |           const diff = (partsA[i] || 0) - (partsB[i] || 0);
146 |           if (diff !== 0) return diff;
147 |         }
148 |         return 0;
149 |       });
150 |     }
151 |   }
152 |   
153 |   return rootTasks;
154 | }
155 | 
156 | // Find main task title (from headers or other places)
157 | function findMainTaskTitle(lines: string[], taskNumber: string): string | null {
158 |   // Look for lines like "### 1. Title" or "## 1. Title"
159 |   for (const line of lines) {
160 |     const headerMatch = line.match(/^#+\s*(\d+)\.\s*(.+)$/);
161 |     if (headerMatch && headerMatch[1] === taskNumber) {
162 |       return headerMatch[2].trim();
163 |     }
164 |   }
165 |   
166 |   // Also support other formats like "1. **Title**"
167 |   for (const line of lines) {
168 |     const boldMatch = line.match(/^(\d+)\.\s*\*\*(.+?)\*\*$/);
169 |     if (boldMatch && boldMatch[1] === taskNumber) {
170 |       return boldMatch[2].trim();
171 |     }
172 |   }
173 |   
174 |   return null;
175 | }
176 | 
177 | export function getFirstUncompletedTask(tasks: Task[]): Task | null {
178 |   for (const task of tasks) {
179 |     // 如果任务有子任务,优先检查子任务
180 |     if (task.subtasks && task.subtasks.length > 0) {
181 |       // 检查是否有未完成的子任务
182 |       const firstUncompletedSubtask = task.subtasks.find(subtask => !subtask.checked);
183 | 
184 |       if (firstUncompletedSubtask) {
185 |         // 无论是虚拟主任务还是真实主任务,都返回第一个未完成的子任务
186 |         return firstUncompletedSubtask;
187 |       }
188 | 
189 |       // 如果所有子任务都完成了,但主任务未完成,返回主任务
190 |       if (!task.checked) {
191 |         return task;
192 |       }
193 |     } else {
194 |       // 没有子任务的情况,直接检查主任务
195 |       if (!task.checked) {
196 |         return task;
197 |       }
198 |     }
199 |   }
200 | 
201 |   return null;
202 | }
203 | 
204 | export function formatTaskForDisplay(task: Task): string {
205 |   let display = `📋 Task ${task.number}: ${task.description}`;
206 |   
207 |   if (task.subtasks && task.subtasks.length > 0) {
208 |     display += '\n\nSubtasks:';
209 |     for (const subtask of task.subtasks) {
210 |       const status = subtask.checked ? '✓' : '☐';
211 |       display += `\n  ${status} ${subtask.number}. ${subtask.description}`;
212 |     }
213 |   }
214 |   
215 |   return display;
216 | }
217 | 
218 | export function formatTaskForFullDisplay(task: Task, content: string): string {
219 |   const lines = content.split('\n');
220 |   const taskLines: string[] = [];
221 |   let capturing = false;
222 |   let indent = '';
223 |   
224 |   for (const line of lines) {
225 |     // Find task start (supports two formats: `1. - [ ] task` or `- [ ] 1. task`)
226 |     const taskPattern1 = new RegExp(`^(\\s*)${task.number}\\.\\s*-\\s*\\[[ x]\\]\\s*`);
227 |     const taskPattern2 = new RegExp(`^(\\s*)-\\s*\\[[ x]\\]\\s*${task.number}\\.\\s*`);
228 |     if (line.match(taskPattern1) || line.match(taskPattern2)) {
229 |       capturing = true;
230 |       taskLines.push(line);
231 |       indent = line.match(/^(\s*)/)?.[1] || '';
232 |       continue;
233 |     }
234 |     
235 |     // If capturing task content
236 |     if (capturing) {
237 |       // Check if reached next task at same or higher level
238 |       const nextTaskPattern = /^(\s*)-\s*\[[ x]\]\s*\d+(\.\d+)*\.\s*/;
239 |       const nextMatch = line.match(nextTaskPattern);
240 |       if (nextMatch) {
241 |         const nextIndent = nextMatch[1] || '';
242 |         if (nextIndent.length <= indent.length) {
243 |           break; // Found same or higher level task, stop capturing
244 |         }
245 |       }
246 |       
247 |       // Continue capturing content belonging to current task
248 |       if (line.trim() === '') {
249 |         taskLines.push(line);
250 |       } else if (line.startsWith(indent + '  ') || line.startsWith(indent + '\t')) {
251 |         // Deeper indented content belongs to current task
252 |         taskLines.push(line);
253 |       } else if (line.match(/^#+\s/)) {
254 |         // Found header, stop capturing
255 |         break;
256 |       } else if (line.match(/^\d+\.\s*-\s*\[[ x]\]/)) {
257 |         // Found other top-level task, stop
258 |         break;
259 |       } else {
260 |         // Other cases continue capturing (might be continuation of task description)
261 |         const isTaskLine = line.match(/^(\s*)-\s*\[[ x]\]/) || line.match(/^(\s*)\d+(\.\d+)*\.\s*-\s*\[[ x]\]/);
262 |         if (isTaskLine) {
263 |           break; // Found other task, stop
264 |         } else if (line.match(/^\s/) && !line.match(/^\s{8,}/)) {
265 |           // If indented but not too deep, might still be current task content
266 |           taskLines.push(line);
267 |         } else {
268 |           break; // Otherwise stop
269 |         }
270 |       }
271 |     }
272 |   }
273 |   
274 |   return taskLines.join('\n').trimEnd();
275 | }
276 | 
277 | // Format task list overview for display
278 | export function formatTaskListOverview(path: string): string {
279 |   try {
280 |     const tasks = parseTasksFile(path);
281 |     if (tasks.length === 0) {
282 |       return 'No tasks found.';
283 |     }
284 |     
285 |     const taskItems = tasks.map(task => {
286 |       const status = task.checked ? '[x]' : '[ ]';
287 |       return `- ${status} ${task.number}. ${task.description}`;
288 |     });
289 |     
290 |     return taskItems.join('\n');
291 |   } catch {
292 |     return 'Error loading tasks list.';
293 |   }
294 | }
```

--------------------------------------------------------------------------------
/src/features/shared/responseBuilder.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { openApiLoader } from './openApiLoader.js';
  2 | import { OpenApiLoader } from './openApiLoader.js';
  3 | import { WorkflowResult } from './mcpTypes.js';
  4 | import { isObject, hasProperty, isArray } from './typeGuards.js';
  5 | import { TaskGuidanceExtractor } from './taskGuidanceTemplate.js';
  6 | 
  7 | // Response builder - builds responses based on OpenAPI specification
  8 | export class ResponseBuilder {
  9 | 
 10 |   // Build initialization response
 11 |   buildInitResponse(path: string, featureName: string): WorkflowResult {
 12 |     const example = openApiLoader.getResponseExample('InitResponse', {
 13 |       success: true
 14 |     });
 15 | 
 16 |     if (!example) {
 17 |       throw new Error('Initialization response template not found');
 18 |     }
 19 | 
 20 |     // Deep copy example
 21 |     const response = JSON.parse(JSON.stringify(example));
 22 | 
 23 |     // Replace variables
 24 |     response.displayText = OpenApiLoader.replaceVariables(response.displayText, {
 25 |       featureName,
 26 |       path,
 27 |       progress: response.progress?.overall || 0
 28 |     });
 29 | 
 30 |     // Update data
 31 |     response.data.path = path;
 32 |     response.data.featureName = featureName;
 33 | 
 34 |     // Resolve resource references
 35 |     if (response.resources) {
 36 |       response.resources = openApiLoader.resolveResources(response.resources);
 37 |     }
 38 | 
 39 |     // Embed resources into display text for better client compatibility
 40 |     const enhancedDisplayText = this.embedResourcesIntoText(response.displayText, response.resources);
 41 | 
 42 |     // Return WorkflowResult format, but include complete OpenAPI response in data
 43 |     return {
 44 |       displayText: enhancedDisplayText,
 45 |       data: response,
 46 |       resources: response.resources
 47 |     };
 48 |   }
 49 | 
 50 |   // Build check response
 51 |   buildCheckResponse(
 52 |     stage: string,
 53 |     progress: unknown,
 54 |     status: unknown,
 55 |     checkResults?: unknown,
 56 |     path?: string,
 57 |     firstTask?: string | null
 58 |   ): WorkflowResult {
 59 |     // Select appropriate example based on status type
 60 |     const statusType = isObject(status) && 'type' in status ? status.type : 'not_started';
 61 | 
 62 |     // Debug info: check examples cache
 63 |     const examplesCount = openApiLoader.getExamplesCount('CheckResponse');
 64 | 
 65 |     const example = openApiLoader.getResponseExample('CheckResponse', {
 66 |       stage,
 67 |       'status.type': statusType
 68 |     });
 69 | 
 70 |     if (!example) {
 71 |       throw new Error(`Check response template not found: stage=${stage}, status=${statusType} (cached examples: ${examplesCount})`);
 72 |     }
 73 | 
 74 |     // Deep copy example
 75 |     const response = JSON.parse(JSON.stringify(example));
 76 | 
 77 |     // Update actual values
 78 |     response.stage = stage;
 79 |     
 80 |     // Convert progress format to comply with OpenAPI specification
 81 |     // If input is WorkflowProgress format, need to convert
 82 |     if (isObject(progress) && hasProperty(progress, 'percentage')) {
 83 |       // Calculate phase progress based on stage status
 84 |       const details = isObject(progress.details) ? progress.details : {};
 85 |       const requirements = isObject(details.requirements) ? details.requirements : {};
 86 |       const design = isObject(details.design) ? details.design : {};
 87 |       const tasks = isObject(details.tasks) ? details.tasks : {};
 88 |       
 89 |       const requirementsProgress = requirements.confirmed || requirements.skipped ? 100 : 0;
 90 |       const designProgress = design.confirmed || design.skipped ? 100 : 0;
 91 |       // Tasks stage: only count as progress if confirmed, not skipped
 92 |       const tasksProgress = tasks.confirmed ? 100 : 0;
 93 |       
 94 |       response.progress = this.calculateProgress(requirementsProgress, designProgress, tasksProgress);
 95 |     } else {
 96 |       // If already in correct format, use directly
 97 |       response.progress = progress;
 98 |     }
 99 |     
100 |     response.status = status;
101 | 
102 |     // If there are check results, update display text
103 |     if (checkResults && response.displayText.includes('The tasks document includes')) {
104 |       // Dynamically build check items list
105 |       const checkItems = this.buildCheckItemsList(checkResults);
106 |       // More precise regex that only matches until next empty line or "Model please" line
107 |       response.displayText = response.displayText.replace(
108 |         /The tasks document includes:[\s\S]*?(?=\n\s*Model please|\n\s*\n\s*Model please|$)/,
109 |         `The tasks document includes:\n${checkItems}\n\n`
110 |       );
111 |     }
112 | 
113 |     // Replace variables including progress
114 |     const variables: Record<string, unknown> = {};
115 |     if (path) {
116 |       variables.path = path;
117 |     }
118 |     if (response.progress && typeof response.progress.overall === 'number') {
119 |       variables.progress = response.progress.overall;
120 |     }
121 |     response.displayText = OpenApiLoader.replaceVariables(response.displayText, variables);
122 |     
123 |     // If completed stage and has uncompleted tasks, add task information
124 |     if (stage === 'completed' && firstTask) {
125 |       response.displayText += `\n\n📄 Next uncompleted task:\n${firstTask}\n\nModel please ask the user: "Ready to start the next task?"`;
126 |     }
127 | 
128 |     // Resolve resource references
129 |     if (response.resources) {
130 |       response.resources = openApiLoader.resolveResources(response.resources);
131 |     }
132 | 
133 |     // Embed resources into display text for better client compatibility
134 |     const enhancedDisplayText = this.embedResourcesIntoText(response.displayText, response.resources);
135 | 
136 |     // Return WorkflowResult format
137 |     return {
138 |       displayText: enhancedDisplayText,
139 |       data: response,
140 |       resources: response.resources
141 |     };
142 |   }
143 | 
144 |   // Build skip response
145 |   buildSkipResponse(stage: string, path?: string, progress?: unknown): WorkflowResult {
146 |     const example = openApiLoader.getResponseExample('SkipResponse', {
147 |       stage
148 |     });
149 | 
150 |     if (!example) {
151 |       throw new Error(`Skip response template not found: stage=${stage}`);
152 |     }
153 | 
154 |     // Deep copy example
155 |     const response = JSON.parse(JSON.stringify(example));
156 |     response.stage = stage;
157 |     
158 |     // Update progress if provided
159 |     if (progress) {
160 |       // Convert progress format to comply with OpenAPI specification
161 |       if (isObject(progress) && hasProperty(progress, 'percentage')) {
162 |         // Calculate phase progress based on stage status
163 |         const details = isObject(progress.details) ? progress.details : {};
164 |         const requirements = isObject(details.requirements) ? details.requirements : {};
165 |         const design = isObject(details.design) ? details.design : {};
166 |         const tasks = isObject(details.tasks) ? details.tasks : {};
167 |         
168 |         const requirementsProgress = requirements.confirmed || requirements.skipped ? 100 : 0;
169 |         const designProgress = design.confirmed || design.skipped ? 100 : 0;
170 |         // Tasks stage: only count as progress if confirmed, not skipped
171 |         const tasksProgress = tasks.confirmed ? 100 : 0;
172 |         
173 |         response.progress = this.calculateProgress(requirementsProgress, designProgress, tasksProgress);
174 |       } else {
175 |         // If already in correct format, use directly
176 |         response.progress = progress;
177 |       }
178 |     }
179 | 
180 |     // Replace variables including progress
181 |     const variables: Record<string, unknown> = {};
182 |     if (path) {
183 |       variables.path = path;
184 |     }
185 |     if (response.progress && typeof response.progress.overall === 'number') {
186 |       variables.progress = response.progress.overall;
187 |     }
188 |     response.displayText = OpenApiLoader.replaceVariables(response.displayText, variables);
189 | 
190 |     // Resolve resource references
191 |     if (response.resources) {
192 |       response.resources = openApiLoader.resolveResources(response.resources);
193 |     }
194 | 
195 |     // Embed resources into display text for better client compatibility
196 |     const enhancedDisplayText = this.embedResourcesIntoText(response.displayText, response.resources);
197 | 
198 |     // Return WorkflowResult format
199 |     return {
200 |       displayText: enhancedDisplayText,
201 |       data: response,
202 |       resources: response.resources
203 |     };
204 |   }
205 | 
206 |   // Build confirm response
207 |   buildConfirmResponse(stage: string, nextStage: string | null, path?: string, firstTaskContent?: string | null, progress?: unknown): WorkflowResult {
208 |     const example = openApiLoader.getResponseExample('ConfirmResponse', {
209 |       stage,
210 |       nextStage: nextStage || null
211 |     });
212 | 
213 |     if (!example) {
214 |       throw new Error(`Confirm response template not found: stage=${stage}`);
215 |     }
216 | 
217 |     // Deep copy example
218 |     const response = JSON.parse(JSON.stringify(example));
219 |     response.stage = stage;
220 |     response.nextStage = nextStage;
221 |     
222 |     // Update progress if provided
223 |     if (progress) {
224 |       // Convert progress format to comply with OpenAPI specification
225 |       if (isObject(progress) && hasProperty(progress, 'percentage')) {
226 |         // Calculate phase progress based on stage status
227 |         const details = isObject(progress.details) ? progress.details : {};
228 |         const requirements = isObject(details.requirements) ? details.requirements : {};
229 |         const design = isObject(details.design) ? details.design : {};
230 |         const tasks = isObject(details.tasks) ? details.tasks : {};
231 |         
232 |         const requirementsProgress = requirements.confirmed || requirements.skipped ? 100 : 0;
233 |         const designProgress = design.confirmed || design.skipped ? 100 : 0;
234 |         // Tasks stage: only count as progress if confirmed, not skipped
235 |         const tasksProgress = tasks.confirmed ? 100 : 0;
236 |         
237 |         response.progress = this.calculateProgress(requirementsProgress, designProgress, tasksProgress);
238 |       } else {
239 |         // If already in correct format, use directly
240 |         response.progress = progress;
241 |       }
242 |     }
243 | 
244 |     // Replace variables including progress
245 |     const variables: Record<string, unknown> = {};
246 |     if (path) {
247 |       variables.path = path;
248 |     }
249 |     if (response.progress && typeof response.progress.overall === 'number') {
250 |       variables.progress = response.progress.overall;
251 |     }
252 |     response.displayText = OpenApiLoader.replaceVariables(response.displayText, variables);
253 | 
254 |     // If tasks stage confirmation and has first task content, append to display text
255 |     if (stage === 'tasks' && nextStage === null && firstTaskContent) {
256 |       // Extract first uncompleted subtask for focused planning
257 |       const firstSubtask = TaskGuidanceExtractor.extractFirstSubtask(firstTaskContent);
258 | 
259 |       // 如果没有找到子任务,从任务内容中提取任务描述
260 |       let effectiveFirstSubtask = firstSubtask;
261 |       if (!effectiveFirstSubtask) {
262 |         // 从 firstTaskContent 中提取任务号和描述
263 |         const taskMatch = firstTaskContent.match(/(\d+(?:\.\d+)*)\.\s*\*?\*?([^*\n]+)/);
264 |         if (taskMatch) {
265 |           effectiveFirstSubtask = `${taskMatch[1]}. ${taskMatch[2].trim()}`;
266 |         } else {
267 |           effectiveFirstSubtask = 'Next task';
268 |         }
269 |       }
270 | 
271 |       // Build guidance text using the template
272 |       const guidanceText = TaskGuidanceExtractor.buildGuidanceText(
273 |         firstTaskContent,
274 |         effectiveFirstSubtask,
275 |         undefined, // no specific task number
276 |         true // is first task
277 |       );
278 | 
279 |       response.displayText += '\n\n' + guidanceText;
280 |     }
281 | 
282 |     // Resolve resource references
283 |     if (response.resources) {
284 |       response.resources = openApiLoader.resolveResources(response.resources);
285 |     }
286 | 
287 |     // Embed resources into display text for better client compatibility
288 |     const enhancedDisplayText = this.embedResourcesIntoText(response.displayText, response.resources);
289 | 
290 |     // Return WorkflowResult format
291 |     return {
292 |       displayText: enhancedDisplayText,
293 |       data: response,
294 |       resources: response.resources
295 |     };
296 |   }
297 | 
298 |   // Build error response
299 |   buildErrorResponse(errorType: string, variables?: Record<string, unknown>): string {
300 |     const template = openApiLoader.getErrorResponse(errorType);
301 |     
302 |     if (!template) {
303 |       return `❌ Error: ${errorType}`;
304 |     }
305 | 
306 |     if (variables) {
307 |       return OpenApiLoader.replaceVariables(template, variables);
308 |     }
309 | 
310 |     return template;
311 |   }
312 | 
313 |   // Calculate progress
314 |   calculateProgress(
315 |     requirementsProgress: number,
316 |     designProgress: number,
317 |     tasksProgress: number
318 |   ): Record<string, unknown> {
319 |     // const rules = openApiLoader.getProgressRules(); // \u672a\u4f7f\u7528
320 |     
321 |     // Use rules defined in OpenAPI to calculate overall progress
322 |     const overall = Math.round(
323 |       requirementsProgress * 0.3 +
324 |       designProgress * 0.3 +
325 |       tasksProgress * 0.4
326 |     );
327 | 
328 |     return {
329 |       overall,
330 |       requirements: requirementsProgress,
331 |       design: designProgress,
332 |       tasks: tasksProgress
333 |     };
334 |   }
335 | 
336 | 
337 | 
338 | 
339 |   // Private method: embed resources into display text
340 |   private embedResourcesIntoText(displayText: string, resources?: unknown[]): string {
341 |     if (!resources || resources.length === 0) {
342 |       return displayText;
343 |     }
344 | 
345 |     // 为每个 resource 构建嵌入文本
346 |     const resourceTexts = resources.map(resource => {
347 |       if (!isObject(resource)) return '';
348 |       const header = `\n\n---\n[Resource: ${resource.title || resource.uri}]\n`;
349 |       const content = resource.text || '';
350 |       return header + content;
351 |     });
352 | 
353 |     // 将资源内容附加到显示文本末尾
354 |     return displayText + resourceTexts.join('');
355 |   }
356 | 
357 |   // Private method: build check items list
358 |   private buildCheckItemsList(checkResults: unknown): string {
359 |     const items: string[] = [];
360 | 
361 |     if (!isObject(checkResults)) return '';
362 |     
363 |     if (isArray(checkResults.requiredSections)) {
364 |       checkResults.requiredSections.forEach((section: unknown) => {
365 |         if (typeof section === 'string') {
366 |           items.push(`- ✓ ${section}`);
367 |         }
368 |       });
369 |     }
370 | 
371 |     if (isArray(checkResults.optionalSections) && checkResults.optionalSections.length > 0) {
372 |       checkResults.optionalSections.forEach((section: unknown) => {
373 |         if (typeof section === 'string') {
374 |           items.push(`- ✓ ${section}`);
375 |         }
376 |       });
377 |     }
378 | 
379 |     return items.join('\n');
380 |   }
381 | }
382 | 
383 | // Export singleton
384 | export const responseBuilder = new ResponseBuilder();
385 | 
```

--------------------------------------------------------------------------------
/src/features/task/completeTask.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Complete task - 统一使用批量完成逻辑
  3 |  */
  4 | 
  5 | import { existsSync, readFileSync, writeFileSync } from 'fs';
  6 | import { join } from 'path';
  7 | import { parseTasksFromContent, getFirstUncompletedTask, formatTaskForFullDisplay, Task } from '../shared/taskParser.js';
  8 | import { responseBuilder } from '../shared/responseBuilder.js';
  9 | import { WorkflowResult } from '../shared/mcpTypes.js';
 10 | import { BatchCompleteTaskResponse } from '../shared/openApiTypes.js';
 11 | import { TaskGuidanceExtractor } from '../shared/taskGuidanceTemplate.js';
 12 | 
 13 | export interface CompleteTaskOptions {
 14 |   path: string;
 15 |   taskNumber: string | string[];
 16 | }
 17 | 
 18 | export async function completeTask(options: CompleteTaskOptions): Promise<WorkflowResult> {
 19 |   const { path, taskNumber } = options;
 20 | 
 21 |   // 统一转换为数组格式进行批量处理
 22 |   const taskNumbers = Array.isArray(taskNumber) ? taskNumber : [taskNumber];
 23 | 
 24 |   if (!existsSync(path)) {
 25 |     return {
 26 |       displayText: responseBuilder.buildErrorResponse('invalidPath', { path }),
 27 |       data: {
 28 |         success: false,
 29 |         error: 'Directory does not exist'
 30 |       }
 31 |     };
 32 |   }
 33 | 
 34 |   const tasksPath = join(path, 'tasks.md');
 35 |   if (!existsSync(tasksPath)) {
 36 |     return {
 37 |       displayText: '❌ Error: tasks.md file does not exist\n\nPlease complete writing the tasks document first.',
 38 |       data: {
 39 |         success: false,
 40 |         error: 'tasks.md does not exist'
 41 |       }
 42 |     };
 43 |   }
 44 | 
 45 |   // 统一使用批量处理逻辑
 46 |   const batchResult = await completeBatchTasks(tasksPath, taskNumbers);
 47 |   return {
 48 |     displayText: batchResult.displayText,
 49 |     data: { ...batchResult }
 50 |   };
 51 | }
 52 | 
 53 | 
 54 | /**
 55 |  * Complete multiple tasks in batch
 56 |  */
 57 | async function completeBatchTasks(tasksPath: string, taskNumbers: string[]): Promise<BatchCompleteTaskResponse> {
 58 |   // Read tasks file
 59 |   const originalContent = readFileSync(tasksPath, 'utf-8');
 60 |   const tasks = parseTasksFromContent(originalContent);
 61 |   
 62 |   // Categorize tasks: already completed, can be completed, cannot be completed
 63 |   const alreadyCompleted: string[] = [];
 64 |   const canBeCompleted: string[] = [];
 65 |   const cannotBeCompleted: Array<{
 66 |     taskNumber: string;
 67 |     reason: string;
 68 |   }> = [];
 69 |   
 70 |   for (const taskNum of taskNumbers) {
 71 |     const targetTask = findTaskByNumber(tasks, taskNum);
 72 |     
 73 |     if (!targetTask) {
 74 |       cannotBeCompleted.push({
 75 |         taskNumber: taskNum,
 76 |         reason: 'Task does not exist'
 77 |       });
 78 |     } else if (targetTask.checked) {
 79 |       alreadyCompleted.push(taskNum);
 80 |     } else if (targetTask.subtasks && targetTask.subtasks.some(s => !s.checked)) {
 81 |       cannotBeCompleted.push({
 82 |         taskNumber: taskNum,
 83 |         reason: 'Has uncompleted subtasks'
 84 |       });
 85 |     } else {
 86 |       canBeCompleted.push(taskNum);
 87 |     }
 88 |   }
 89 |   
 90 |   // If there are tasks that cannot be completed (excluding already completed), return error
 91 |   if (cannotBeCompleted.length > 0) {
 92 |     const errorMessages = cannotBeCompleted
 93 |       .map(v => `- ${v.taskNumber}: ${v.reason}`)
 94 |       .join('\n');
 95 |     
 96 |     return {
 97 |       success: false,
 98 |       completedTasks: [],
 99 |       alreadyCompleted: [],
100 |       failedTasks: cannotBeCompleted,
101 |       displayText: `❌ Batch task completion failed\n\nThe following tasks cannot be completed:\n${errorMessages}\n\nPlease resolve these issues and try again.`
102 |     };
103 |   }
104 |   
105 |   // If no tasks can be completed but there are already completed tasks, still return success
106 |   if (canBeCompleted.length === 0 && alreadyCompleted.length > 0) {
107 |     const allTasks = parseTasksFromContent(originalContent);
108 |     const nextTask = getFirstUncompletedTask(allTasks);
109 |     
110 |     const alreadyCompletedText = alreadyCompleted
111 |       .map(t => `- ${t} (already completed)`)
112 |       .join('\n');
113 |     
114 |     const displayText = `${TaskGuidanceExtractor.getCompletionMessage('batchCompleted')}\n\nThe following tasks were already completed:\n${alreadyCompletedText}\n\n${nextTask ? `Next task: ${nextTask.number}. ${nextTask.description}` : TaskGuidanceExtractor.getCompletionMessage('allCompleted')}`;
115 |     
116 |     return {
117 |       success: true,
118 |       completedTasks: [],
119 |       alreadyCompleted,
120 |       nextTask: nextTask ? {
121 |         number: nextTask.number,
122 |         description: nextTask.description
123 |       } : undefined,
124 |       hasNextTask: nextTask !== null,
125 |       displayText
126 |     };
127 |   }
128 |   
129 |   // Execution phase: complete tasks in dependency order
130 |   let currentContent = originalContent;
131 |   const actuallyCompleted: string[] = [];
132 |   const results: Array<{
133 |     taskNumber: string;
134 |     success: boolean;
135 |     status: 'completed' | 'already_completed' | 'failed';
136 |   }> = [];
137 |   
138 |   try {
139 |     // Sort by task number, ensure parent tasks are processed after subtasks (avoid dependency conflicts)
140 |     const sortedTaskNumbers = [...canBeCompleted].sort((a, b) => {
141 |       // Subtasks first (numbers with more dots have priority)
142 |       const aDepth = a.split('.').length;
143 |       const bDepth = b.split('.').length;
144 |       if (aDepth !== bDepth) {
145 |         return bDepth - aDepth; // Process deeper levels first
146 |       }
147 |       return a.localeCompare(b); // Same depth, sort by string
148 |     });
149 |     
150 |     for (const taskNum of sortedTaskNumbers) {
151 |       const updatedContent = markTaskAsCompleted(currentContent, taskNum);
152 |       
153 |       if (!updatedContent) {
154 |         // This should not happen as we have already validated
155 |         throw new Error(`Unexpected error: Task ${taskNum} could not be marked`);
156 |       }
157 |       
158 |       currentContent = updatedContent;
159 |       actuallyCompleted.push(taskNum);
160 |       results.push({
161 |         taskNumber: taskNum,
162 |         success: true,
163 |         status: 'completed' as const
164 |       });
165 |     }
166 |     
167 |     // Add results for already completed tasks
168 |     for (const taskNum of alreadyCompleted) {
169 |       results.push({
170 |         taskNumber: taskNum,
171 |         success: true,
172 |         status: 'already_completed' as const
173 |       });
174 |     }
175 |     
176 |     // All tasks completed successfully, save file
177 |     if (actuallyCompleted.length > 0) {
178 |       writeFileSync(tasksPath, currentContent, 'utf-8');
179 |     }
180 |     
181 |     // Build success response
182 |     const allTasks = parseTasksFromContent(currentContent);
183 |     const nextTask = getFirstUncompletedTask(allTasks);
184 |     
185 |     // Build detailed completion information
186 |     let completedInfo = '';
187 |     if (actuallyCompleted.length > 0) {
188 |       completedInfo += 'Newly completed tasks:\n' + actuallyCompleted.map(t => `- ${t}`).join('\n');
189 |     }
190 |     if (alreadyCompleted.length > 0) {
191 |       if (completedInfo) completedInfo += '\n\n';
192 |       completedInfo += 'Already completed tasks:\n' + alreadyCompleted.map(t => `- ${t} (already completed)`).join('\n');
193 |     }
194 |     
195 |     let displayText = `${TaskGuidanceExtractor.getCompletionMessage('batchSucceeded')}\n\n${completedInfo}`;
196 |     
197 |     // Add enhanced guidance for next task
198 |     if (nextTask) {
199 |       // 获取主任务的完整内容用于显示任务块
200 |       let mainTask = nextTask;
201 |       let mainTaskContent = '';
202 | 
203 |       // 如果当前是子任务,需要找到对应的主任务
204 |       if (nextTask.number.includes('.')) {
205 |         const mainTaskNumber = nextTask.number.split('.')[0];
206 |         const mainTaskObj = allTasks.find(task => task.number === mainTaskNumber);
207 |         if (mainTaskObj) {
208 |           mainTask = mainTaskObj;
209 |           mainTaskContent = formatTaskForFullDisplay(mainTask, currentContent);
210 |         } else {
211 |           // 如果找不到主任务,使用当前任务
212 |           mainTaskContent = formatTaskForFullDisplay(nextTask, currentContent);
213 |         }
214 |       } else {
215 |         // 如果本身就是主任务,直接使用
216 |         mainTaskContent = formatTaskForFullDisplay(nextTask, currentContent);
217 |       }
218 | 
219 |       // 构建下一个具体子任务的描述(用于指导文本)
220 |       let effectiveFirstSubtask: string;
221 |       let actualNextSubtask: Task | null = null;
222 | 
223 |       if (nextTask.number.includes('.')) {
224 |         // 如果下一个任务是子任务,直接使用
225 |         actualNextSubtask = nextTask;
226 |       } else {
227 |         // 如果下一个任务是主任务,找到第一个未完成的子任务
228 |         if (mainTask.subtasks && mainTask.subtasks.length > 0) {
229 |           actualNextSubtask = mainTask.subtasks.find(subtask => !subtask.checked) || null;
230 |         }
231 |       }
232 | 
233 |       if (actualNextSubtask) {
234 |         // 使用具体的子任务构建指导文本,包含完整内容
235 |         const nextSubtaskContent = formatTaskForFullDisplay(actualNextSubtask, currentContent);
236 | 
237 |         if (nextSubtaskContent.trim()) {
238 |           // 如果能获取到完整内容,直接使用
239 |           effectiveFirstSubtask = nextSubtaskContent.trim();
240 |         } else {
241 |           // 如果获取不到完整内容,手动构建
242 |           effectiveFirstSubtask = `- [ ] ${actualNextSubtask.number} ${actualNextSubtask.description}`;
243 | 
244 |           // 从主任务内容中提取这个子任务的详细信息
245 |           const mainTaskLines = mainTaskContent.split('\n');
246 |           let capturing = false;
247 |           let taskIndent = '';
248 | 
249 |           for (const line of mainTaskLines) {
250 |             // 找到目标子任务的开始
251 |             if (line.includes(`${actualNextSubtask.number} ${actualNextSubtask.description}`) ||
252 |                 line.includes(`${actualNextSubtask.number}. ${actualNextSubtask.description}`)) {
253 |               capturing = true;
254 |               taskIndent = line.match(/^(\s*)/)?.[1] || '';
255 |               continue;
256 |             }
257 | 
258 |             // 如果正在捕获内容
259 |             if (capturing) {
260 |               const lineIndent = line.match(/^(\s*)/)?.[1] || '';
261 | 
262 |               // 如果遇到下一个任务(同级或更高级),停止捕获
263 |               if (line.includes('[ ]') && lineIndent.length <= taskIndent.length) {
264 |                 break;
265 |               }
266 | 
267 |               // 如果是更深层次的内容,添加到结果中
268 |               if (lineIndent.length > taskIndent.length && line.trim()) {
269 |                 effectiveFirstSubtask += `\n${line}`;
270 |               }
271 |             }
272 |           }
273 |         }
274 |       } else {
275 |         // 如果找不到具体的子任务,使用主任务
276 |         effectiveFirstSubtask = `${nextTask.number}. ${nextTask.description}`;
277 |       }
278 | 
279 |       // Build guidance text using the template
280 |       const guidanceText = TaskGuidanceExtractor.buildGuidanceText(
281 |         mainTaskContent,  // 显示主任务块
282 |         effectiveFirstSubtask,  // 用于指导文本的具体子任务
283 |         undefined, // no specific task number for batch
284 |         false // not first task
285 |       );
286 | 
287 |       displayText += '\n\n' + guidanceText;
288 |     } else {
289 |       displayText += '\n\n' + TaskGuidanceExtractor.getCompletionMessage('allCompleted');
290 |     }
291 |     
292 |     return {
293 |       success: true,
294 |       completedTasks: actuallyCompleted,
295 |       alreadyCompleted,
296 |       failedTasks: [],
297 |       results,
298 |       nextTask: nextTask ? {
299 |         number: nextTask.number,
300 |         description: nextTask.description
301 |       } : undefined,
302 |       hasNextTask: nextTask !== null,
303 |       displayText
304 |     };
305 |     
306 |   } catch (error) {
307 |     // Execution failed, need to rollback to original state
308 |     if (actuallyCompleted.length > 0) {
309 |       writeFileSync(tasksPath, originalContent, 'utf-8');
310 |     }
311 |     
312 |     return {
313 |       success: false,
314 |       completedTasks: [],
315 |       alreadyCompleted: [],
316 |       failedTasks: [{
317 |         taskNumber: 'batch',
318 |         reason: error instanceof Error ? error.message : String(error)
319 |       }],
320 |       results,
321 |       displayText: `❌ Batch task execution failed\n\nError: ${error instanceof Error ? error.message : String(error)}\n\nRolled back to original state.`
322 |     };
323 |   }
324 | }
325 | 
326 | 
327 | 
328 | /**
329 |  * Mark task as completed
330 |  */
331 | function markTaskAsCompleted(content: string, taskNumber: string): string | null {
332 |   const lines = content.split('\n');
333 |   const tasks = parseTasksFromContent(content);
334 |   let found = false;
335 |   
336 |   // Find target task (including subtasks)
337 |   const targetTask = findTaskByNumber(tasks, taskNumber);
338 |   if (!targetTask) {
339 |     return null;
340 |   }
341 |   
342 |   // Build set of task numbers to mark
343 |   const numbersToMark = new Set<string>();
344 |   numbersToMark.add(taskNumber);
345 |   
346 |   // If it's a leaf task, check if parent task should be auto-marked
347 |   const parentNumber = taskNumber.substring(0, taskNumber.lastIndexOf('.'));
348 |   if (parentNumber && taskNumber.includes('.')) {
349 |     const parentTask = findTaskByNumber(tasks, parentNumber);
350 |     if (parentTask && parentTask.subtasks) {
351 |       // Check if all sibling tasks are completed
352 |       const allSiblingsCompleted = parentTask.subtasks
353 |         .filter(s => s.number !== taskNumber)
354 |         .every(s => s.checked);
355 |       
356 |       if (allSiblingsCompleted) {
357 |         numbersToMark.add(parentNumber);
358 |       }
359 |     }
360 |   }
361 |   
362 |   // Mark all related tasks
363 |   for (let i = 0; i < lines.length; i++) {
364 |     const line = lines[i];
365 |     
366 |     // Skip already completed tasks
367 |     if (!line.includes('[ ]')) continue;
368 |     
369 |     // Check if line contains task number to mark
370 |     for (const num of numbersToMark) {
371 |       // More robust matching strategy: as long as the line contains both task number and checkbox
372 |       // Don't care about their relative position and format details
373 |       if (containsTaskNumber(line, num)) {
374 |         lines[i] = line.replace('[ ]', '[x]');
375 |         found = true;
376 |         break;
377 |       }
378 |     }
379 |   }
380 |   
381 |   return found ? lines.join('\n') : null;
382 | }
383 | 
384 | /**
385 |  * Check if line contains specified task number
386 |  * Use flexible matching strategy, ignore format details
387 |  */
388 | function containsTaskNumber(line: string, taskNumber: string): boolean {
389 |   // Remove checkbox part to avoid interference with matching
390 |   const lineWithoutCheckbox = line.replace(/\[[xX ]\]/g, '');
391 |   
392 |   // Use word boundary to ensure matching complete task number
393 |   // For example: won't mistakenly match "11.1" as "1.1"
394 |   const escapedNumber = escapeRegExp(taskNumber);
395 |   const regex = new RegExp(`\\b${escapedNumber}\\b`);
396 |   
397 |   return regex.test(lineWithoutCheckbox);
398 | }
399 | 
400 | /**
401 |  * Escape regex special characters
402 |  */
403 | function escapeRegExp(string: string): string {
404 |   return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
405 | }
406 | 
407 | /**
408 |  * Recursively find task (including subtasks)
409 |  */
410 | function findTaskByNumber(tasks: Task[], targetNumber: string): Task | null {
411 |   for (const task of tasks) {
412 |     if (task.number === targetNumber) {
413 |       return task;
414 |     }
415 |     
416 |     // Recursively search subtasks
417 |     if (task.subtasks) {
418 |       const found = findTaskByNumber(task.subtasks, targetNumber);
419 |       if (found) {
420 |         return found;
421 |       }
422 |     }
423 |   }
424 |   
425 |   return null;
426 | }
```

--------------------------------------------------------------------------------
/scripts/generateOpenApiWebUI.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env tsx
  2 | /**
  3 |  * Generate WebUI directly from OpenAPI specification
  4 |  * No intermediate JSON files needed, directly parse YAML to generate HTML
  5 |  */
  6 | 
  7 | import * as fs from 'fs';
  8 | import * as path from 'path';
  9 | import * as yaml from 'js-yaml';
 10 | import { fileURLToPath } from 'url';
 11 | import { dirname } from 'path';
 12 | 
 13 | const __filename = fileURLToPath(import.meta.url);
 14 | const __dirname = dirname(__filename);
 15 | 
 16 | // Read OpenAPI specification
 17 | const specPath = path.join(__dirname, '../api/spec-workflow.openapi.yaml');
 18 | const spec = yaml.load(fs.readFileSync(specPath, 'utf8')) as any;
 19 | 
 20 | // Scenario type definition
 21 | interface Scenario {
 22 |   id: string;
 23 |   title: string;
 24 |   description: string;
 25 |   responseType: string;
 26 |   example: any;
 27 |   schema: any;
 28 | }
 29 | 
 30 | // Extract all scenarios from OpenAPI
 31 | function extractScenarios(): Scenario[] {
 32 |   const allScenarios: Scenario[] = [];
 33 |   
 34 |   // First collect all scenarios, without numbering
 35 |   const responseSchemas = ['InitResponse', 'CheckResponse', 'SkipResponse', 'ConfirmResponse', 'CompleteTaskResponse'];
 36 |   
 37 |   for (const schemaName of responseSchemas) {
 38 |     const schema = spec.components.schemas[schemaName];
 39 |     if (!schema || !schema.examples) continue;
 40 | 
 41 |     schema.examples.forEach((example: any, index: number) => {
 42 |       allScenarios.push({
 43 |         id: `${schemaName.toLowerCase()}-${index + 1}`,
 44 |         title: getScenarioTitle(schemaName, example),
 45 |         description: getScenarioDescription(schemaName, example),
 46 |         responseType: schemaName,
 47 |         example: example,
 48 |         schema: schema
 49 |       });
 50 |     });
 51 |   }
 52 | 
 53 |   // Add error response scenarios
 54 |   if (spec['x-error-responses']) {
 55 |     for (const [errorType, errorDef] of Object.entries(spec['x-error-responses']) as [string, any][]) {
 56 |       allScenarios.push({
 57 |         id: `error-${errorType}`,
 58 |         title: `Error: ${errorType}`,
 59 |         description: 'Error response example',
 60 |         responseType: 'ErrorResponse',
 61 |         example: { displayText: errorDef.displayText },
 62 |         schema: null
 63 |       });
 64 |     }
 65 |   }
 66 | 
 67 |   // Reorder scenarios by workflow sequence
 68 |   const orderedScenarios: Scenario[] = [];
 69 |   
 70 |   // 1. Initialization
 71 |   const initScenario = allScenarios.find(s => s.responseType === 'InitResponse');
 72 |   if (initScenario) orderedScenarios.push(initScenario);
 73 |   
 74 |   // 2. requirements stage
 75 |   const reqNotEdited = allScenarios.find(s => 
 76 |     s.responseType === 'CheckResponse' && 
 77 |     s.example.stage === 'requirements' && 
 78 |     s.example.status?.type === 'not_edited' &&
 79 |     !s.example.status?.skipIntent
 80 |   );
 81 |   if (reqNotEdited) orderedScenarios.push(reqNotEdited);
 82 |   
 83 |   const reqReadyToConfirm = allScenarios.find(s => 
 84 |     s.responseType === 'CheckResponse' && 
 85 |     s.example.stage === 'requirements' && 
 86 |     s.example.status?.type === 'ready_to_confirm' &&
 87 |     !s.example.status?.userApproved
 88 |   );
 89 |   if (reqReadyToConfirm) orderedScenarios.push(reqReadyToConfirm);
 90 |   
 91 |   const confirmReq = allScenarios.find(s => 
 92 |     s.responseType === 'ConfirmResponse' && 
 93 |     s.example.stage === 'requirements'
 94 |   );
 95 |   if (confirmReq) orderedScenarios.push(confirmReq);
 96 |   
 97 |   // 3. design stage
 98 |   const designNotEdited = allScenarios.find(s => 
 99 |     s.responseType === 'CheckResponse' && 
100 |     s.example.stage === 'design' && 
101 |     s.example.status?.type === 'not_edited'
102 |   );
103 |   if (designNotEdited) orderedScenarios.push(designNotEdited);
104 |   
105 |   const designReadyToConfirm = allScenarios.find(s => 
106 |     s.responseType === 'CheckResponse' && 
107 |     s.example.stage === 'design' && 
108 |     s.example.status?.type === 'ready_to_confirm'
109 |   );
110 |   if (designReadyToConfirm) orderedScenarios.push(designReadyToConfirm);
111 |   
112 |   const confirmDesign = allScenarios.find(s => 
113 |     s.responseType === 'ConfirmResponse' && 
114 |     s.example.stage === 'design'
115 |   );
116 |   if (confirmDesign) orderedScenarios.push(confirmDesign);
117 |   
118 |   // 4. tasks stage
119 |   const tasksNotEdited = allScenarios.find(s => 
120 |     s.responseType === 'CheckResponse' && 
121 |     s.example.stage === 'tasks' && 
122 |     s.example.status?.type === 'not_edited'
123 |   );
124 |   if (tasksNotEdited) orderedScenarios.push(tasksNotEdited);
125 |   
126 |   const tasksReadyToConfirm = allScenarios.find(s => 
127 |     s.responseType === 'CheckResponse' && 
128 |     s.example.stage === 'tasks' && 
129 |     s.example.status?.type === 'ready_to_confirm'
130 |   );
131 |   if (tasksReadyToConfirm) orderedScenarios.push(tasksReadyToConfirm);
132 |   
133 |   const confirmTasks = allScenarios.find(s => 
134 |     s.responseType === 'ConfirmResponse' && 
135 |     s.example.stage === 'tasks'
136 |   );
137 |   if (confirmTasks) orderedScenarios.push(confirmTasks);
138 |   
139 |   // 5. Complete task scenarios
140 |   const completeTaskScenarios = allScenarios.filter(s => s.responseType === 'CompleteTaskResponse');
141 |   orderedScenarios.push(...completeTaskScenarios);
142 |   
143 |   // 6. Skip related scenarios
144 |   // First add skip intent detection
145 |   const reqSkipIntent = allScenarios.find(s => 
146 |     s.responseType === 'CheckResponse' && 
147 |     s.example.stage === 'requirements' && 
148 |     s.example.status?.skipIntent
149 |   );
150 |   if (reqSkipIntent) orderedScenarios.push(reqSkipIntent);
151 |   
152 |   // Then add actual skip responses
153 |   const skipScenarios = allScenarios.filter(s => s.responseType === 'SkipResponse');
154 |   orderedScenarios.push(...skipScenarios);
155 |   
156 |   // 7. Error scenarios
157 |   const errorScenarios = allScenarios.filter(s => s.responseType === 'ErrorResponse');
158 |   orderedScenarios.push(...errorScenarios);
159 |   
160 |   // Renumber
161 |   orderedScenarios.forEach((scenario, index) => {
162 |     scenario.title = `${index + 1}. ${scenario.title}`;
163 |   });
164 |   
165 |   return orderedScenarios;
166 | }
167 | 
168 | // Get scenario title
169 | function getScenarioTitle(schemaName: string, example: any): string {
170 |   const titles: Record<string, (ex: any) => string> = {
171 |     InitResponse: (ex) => ex.success ? 'Initialization Successful' : 'Initialization Scenario',
172 |     CheckResponse: (ex) => {
173 |       if (ex.status?.type === 'not_edited' && ex.status?.skipIntent) return `${ex.stage || 'Stage'} Skip Confirmation`;
174 |       if (ex.status?.type === 'not_edited') return `${ex.stage || 'Stage'} Not Edited`;
175 |       if (ex.status?.type === 'ready_to_confirm' && ex.status?.userApproved) return `${ex.stage || 'Stage'} User Approved`;
176 |       if (ex.status?.type === 'ready_to_confirm') return `${ex.stage || 'Stage'} Ready to Confirm`;
177 |       return 'Check Status';
178 |     },
179 |     SkipResponse: (ex) => `Skip ${ex.stage || 'Stage'}`,
180 |     ConfirmResponse: (ex) => `Confirm ${ex.stage || 'Stage'}`,
181 |     CompleteTaskResponse: (ex) => ex.hasNextTask ? 'Complete Task (Has Next)' : 'Complete Task (All Done)'
182 |   };
183 | 
184 |   const titleFn = titles[schemaName];
185 |   return titleFn ? titleFn(example) : schemaName;
186 | }
187 | 
188 | // Get scenario description
189 | function getScenarioDescription(schemaName: string, example: any): string {
190 |   const descriptions: Record<string, string> = {
191 |     InitResponse: 'Response for initializing workflow',
192 |     CheckResponse: 'Response for checking current status',
193 |     SkipResponse: 'Response for skipping stage',
194 |     ConfirmResponse: 'Response for confirming stage completion',
195 |     CompleteTaskResponse: 'Response for marking task as complete'
196 |   };
197 |   return descriptions[schemaName] || schemaName;
198 | }
199 | 
200 | // Generate HTML
201 | function generateHTML(scenarios: Scenario[]): string {
202 |   const scenarioCards = scenarios.map(scenario => `
203 |     <div class="scenario-card">
204 |       <h3>${scenario.title}</h3>
205 |       <p class="description">${scenario.description}</p>
206 |       
207 |       <div class="response-type">Response Type: <code>${scenario.responseType}</code></div>
208 |       
209 |       <div class="example-section">
210 |         <h4>Example Response:</h4>
211 |         <pre class="example-content">${JSON.stringify(scenario.example, null, 2)}</pre>
212 |       </div>
213 |       
214 |       ${scenario.example.displayText ? `
215 |       <div class="display-text-section">
216 |         <h4>Display Text:</h4>
217 |         <pre class="display-text">${scenario.example.displayText}</pre>
218 |       </div>
219 |       ` : ''}
220 |       
221 |       ${scenario.example.resources ? `
222 |       <div class="resources-section">
223 |         <h4>Included Resources:</h4>
224 |         <ul>
225 |           ${scenario.example.resources.map((r: any) => `<li>${r.ref || r.id || 'Unknown Resource'}</li>`).join('')}
226 |         </ul>
227 |       </div>
228 |       ` : ''}
229 |     </div>
230 |   `).join('');
231 | 
232 |   return `<!DOCTYPE html>
233 | <html lang="en">
234 | <head>
235 |     <meta charset="UTF-8">
236 |     <meta name="viewport" content="width=device-width, initial-scale=1.0">
237 |     <title>Spec Workflow - OpenAPI Response Examples</title>
238 |     <style>
239 |         * {
240 |             margin: 0;
241 |             padding: 0;
242 |             box-sizing: border-box;
243 |         }
244 |         
245 |         body {
246 |             font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
247 |             background: #f6f8fa;
248 |             color: #24292e;
249 |             line-height: 1.6;
250 |             padding: 20px;
251 |         }
252 |         
253 |         .container {
254 |             max-width: 1400px;
255 |             margin: 0 auto;
256 |         }
257 |         
258 |         h1 {
259 |             text-align: center;
260 |             font-size: 2.5em;
261 |             margin-bottom: 10px;
262 |             color: #0366d6;
263 |         }
264 |         
265 |         .subtitle {
266 |             text-align: center;
267 |             color: #586069;
268 |             margin-bottom: 20px;
269 |             font-size: 1.1em;
270 |         }
271 |         
272 |         .info-box {
273 |             background: #f0f7ff;
274 |             border: 1px solid #c8e1ff;
275 |             border-radius: 6px;
276 |             padding: 16px;
277 |             margin-bottom: 30px;
278 |             text-align: center;
279 |         }
280 |         
281 |         .grid {
282 |             display: grid;
283 |             grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
284 |             gap: 20px;
285 |             margin-bottom: 40px;
286 |         }
287 |         
288 |         .scenario-card {
289 |             background: white;
290 |             border: 1px solid #e1e4e8;
291 |             border-radius: 8px;
292 |             padding: 20px;
293 |             box-shadow: 0 1px 3px rgba(0,0,0,0.05);
294 |             transition: all 0.3s ease;
295 |         }
296 |         
297 |         .scenario-card:hover {
298 |             box-shadow: 0 4px 12px rgba(0,0,0,0.1);
299 |             transform: translateY(-2px);
300 |         }
301 |         
302 |         .scenario-card h3 {
303 |             color: #0366d6;
304 |             margin-bottom: 10px;
305 |             font-size: 1.3em;
306 |         }
307 |         
308 |         .description {
309 |             color: #586069;
310 |             margin-bottom: 15px;
311 |         }
312 |         
313 |         .response-type {
314 |             background: #f3f4f6;
315 |             padding: 4px 8px;
316 |             border-radius: 4px;
317 |             font-size: 0.9em;
318 |             margin-bottom: 15px;
319 |             display: inline-block;
320 |         }
321 |         
322 |         code {
323 |             background: #f3f4f6;
324 |             padding: 2px 4px;
325 |             border-radius: 3px;
326 |             font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
327 |         }
328 |         
329 |         .example-section, .display-text-section, .resources-section {
330 |             margin-top: 15px;
331 |         }
332 |         
333 |         h4 {
334 |             color: #24292e;
335 |             font-size: 1em;
336 |             margin-bottom: 8px;
337 |         }
338 |         
339 |         pre {
340 |             background: #f6f8fa;
341 |             border: 1px solid #e1e4e8;
342 |             border-radius: 6px;
343 |             padding: 12px;
344 |             overflow-x: auto;
345 |             font-size: 0.85em;
346 |             font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
347 |         }
348 |         
349 |         .example-content {
350 |             max-height: 300px;
351 |             overflow-y: auto;
352 |         }
353 |         
354 |         .display-text {
355 |             background: #f0f9ff;
356 |             border-color: #bae6fd;
357 |             white-space: pre-wrap;
358 |         }
359 |         
360 |         .resources-section ul {
361 |             list-style: none;
362 |             padding-left: 0;
363 |         }
364 |         
365 |         .resources-section li {
366 |             background: #e7f5ff;
367 |             padding: 4px 8px;
368 |             border-radius: 4px;
369 |             margin-bottom: 4px;
370 |             font-size: 0.9em;
371 |         }
372 |         
373 |         .stats {
374 |             text-align: center;
375 |             margin-top: 40px;
376 |             padding: 20px;
377 |             background: white;
378 |             border-radius: 8px;
379 |             border: 1px solid #e1e4e8;
380 |         }
381 |         
382 |         .stats-grid {
383 |             display: grid;
384 |             grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
385 |             gap: 20px;
386 |             margin-top: 15px;
387 |         }
388 |         
389 |         .stat-item {
390 |             text-align: center;
391 |         }
392 |         
393 |         .stat-number {
394 |             font-size: 2em;
395 |             font-weight: bold;
396 |             color: #0366d6;
397 |         }
398 |         
399 |         .stat-label {
400 |             color: #586069;
401 |             font-size: 0.9em;
402 |         }
403 |     </style>
404 | </head>
405 | <body>
406 |     <div class="container">
407 |         <h1>Spec Workflow - OpenAPI Response Examples</h1>
408 |         <p class="subtitle">All response scenarios automatically generated from OpenAPI specification</p>
409 |         
410 |         <div class="info-box">
411 |             <p>📍 Data Source: <code>api/spec-workflow.openapi.yaml</code></p>
412 |             <p>🔄 Last Updated: ${new Date().toLocaleString('en-US')}</p>
413 |         </div>
414 |         
415 |         <div class="grid">
416 |             ${scenarioCards}
417 |         </div>
418 |         
419 |         <div class="stats">
420 |             <h2>Statistics</h2>
421 |             <div class="stats-grid">
422 |                 <div class="stat-item">
423 |                     <div class="stat-number">${scenarios.length}</div>
424 |                     <div class="stat-label">Total Scenarios</div>
425 |                 </div>
426 |                 <div class="stat-item">
427 |                     <div class="stat-number">${responseSchemas.length}</div>
428 |                     <div class="stat-label">Response Types</div>
429 |                 </div>
430 |                 <div class="stat-item">
431 |                     <div class="stat-number">${Object.keys(spec['x-error-responses'] || {}).length}</div>
432 |                     <div class="stat-label">Error Types</div>
433 |                 </div>
434 |             </div>
435 |         </div>
436 |     </div>
437 | </body>
438 | </html>`;
439 | }
440 | 
441 | // Main function
442 | function main() {
443 |   console.log('🔍 Extracting scenarios from OpenAPI specification...');
444 |   
445 |   const scenarios = extractScenarios();
446 |   console.log(`✅ Extracted ${scenarios.length} scenarios`);
447 |   
448 |   const html = generateHTML(scenarios);
449 |   
450 |   const outputPath = path.join(__dirname, '../webui/prompt-grid.html');
451 |   fs.writeFileSync(outputPath, html, 'utf8');
452 |   
453 |   console.log('✅ WebUI generated to:', outputPath);
454 |   console.log('🚀 Open this file in browser to view all response examples');
455 | }
456 | 
457 | // Define response type list
458 | const responseSchemas = ['InitResponse', 'CheckResponse', 'SkipResponse', 'ConfirmResponse', 'CompleteTaskResponse'];
459 | 
460 | main();
```
Page 1/2FirstPrevNextLast