mspec-cli 4.0.1__tar.gz → 4.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mspec_cli-4.0.1/src/mspec_cli.egg-info → mspec_cli-4.0.5}/PKG-INFO +24 -3
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/README.md +23 -2
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/pyproject.toml +1 -1
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/commands/doctor.py +23 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/commands/init.py +59 -18
- mspec_cli-4.0.5/src/mspec/core/openspec_manager.py +232 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/templates/config.yaml +111 -60
- {mspec_cli-4.0.1 → mspec_cli-4.0.5/src/mspec_cli.egg-info}/PKG-INFO +24 -3
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec_cli.egg-info/SOURCES.txt +1 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/LICENSE +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/MANIFEST.in +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/setup.cfg +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/__init__.py +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/__main__.py +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/cli.py +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/commands/__init__.py +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/commands/update.py +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/core/__init__.py +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/core/config.py +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/core/template.py +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/templates/design-issue-taxonomy.md +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/templates/design-issues.md +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/templates/requirement-issue-taxonomy.md +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/templates/requirement-issues.md +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/utils/__init__.py +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec/utils/console.py +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec_cli.egg-info/dependency_links.txt +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec_cli.egg-info/entry_points.txt +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec_cli.egg-info/requires.txt +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/src/mspec_cli.egg-info/top_level.txt +0 -0
- {mspec_cli-4.0.1 → mspec_cli-4.0.5}/tests/test_init.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mspec-cli
|
|
3
|
-
Version: 4.0.
|
|
3
|
+
Version: 4.0.5
|
|
4
4
|
Summary: mspec-cli - 成员规范驱动开发工作流 CLI 工具
|
|
5
5
|
Author-email: shirayner <team@mspec.io>
|
|
6
6
|
License: MIT
|
|
@@ -57,8 +57,29 @@ Dynamic: license-file
|
|
|
57
57
|
|
|
58
58
|
## 文档
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
📚 **[完整文档 →](docs/)**
|
|
61
|
+
|
|
62
|
+
### 新手入门
|
|
63
|
+
- [📖 安装指南](docs/getting-started/installation.md) - 系统要求与安装步骤
|
|
64
|
+
- [⚡ 5分钟快速开始](docs/getting-started/quickstart.md) - 体验完整流程
|
|
65
|
+
- [🎯 第一个完整变更](docs/getting-started/first-change.md) - 从零到交付的详细教程
|
|
66
|
+
|
|
67
|
+
### 完整指南
|
|
68
|
+
- [💡 核心概念](docs/guide/concepts.md) - Change、Capability、问题分类学
|
|
69
|
+
- [🔧 CLI 命令详解](docs/guide/cli-commands.md) - init、update、doctor
|
|
70
|
+
- [📋 工作流详解](docs/guide/) - 探索 → 澄清 → 提案 → 设计 → 实施 → 归档
|
|
71
|
+
|
|
72
|
+
### 实战教程
|
|
73
|
+
- [🛠️ Todo 应用开发](docs/tutorial/todo-app/) - 从零开发完整项目
|
|
74
|
+
|
|
75
|
+
### 参考手册
|
|
76
|
+
- [📖 CLI 命令速查](docs/reference/cli-reference.md)
|
|
77
|
+
- [⚙️ 配置参考](docs/reference/config-reference.md)
|
|
78
|
+
- [🗂️ 问题分类学参考](docs/reference/taxonomy-reference.md)
|
|
79
|
+
|
|
80
|
+
### 其他
|
|
81
|
+
- [❓ 常见问题 (FAQ)](docs/faq.md)
|
|
82
|
+
- [🐛 故障排除](docs/troubleshooting.md)
|
|
62
83
|
|
|
63
84
|
## 快速开始
|
|
64
85
|
|
|
@@ -18,8 +18,29 @@
|
|
|
18
18
|
|
|
19
19
|
## 文档
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
📚 **[完整文档 →](docs/)**
|
|
22
|
+
|
|
23
|
+
### 新手入门
|
|
24
|
+
- [📖 安装指南](docs/getting-started/installation.md) - 系统要求与安装步骤
|
|
25
|
+
- [⚡ 5分钟快速开始](docs/getting-started/quickstart.md) - 体验完整流程
|
|
26
|
+
- [🎯 第一个完整变更](docs/getting-started/first-change.md) - 从零到交付的详细教程
|
|
27
|
+
|
|
28
|
+
### 完整指南
|
|
29
|
+
- [💡 核心概念](docs/guide/concepts.md) - Change、Capability、问题分类学
|
|
30
|
+
- [🔧 CLI 命令详解](docs/guide/cli-commands.md) - init、update、doctor
|
|
31
|
+
- [📋 工作流详解](docs/guide/) - 探索 → 澄清 → 提案 → 设计 → 实施 → 归档
|
|
32
|
+
|
|
33
|
+
### 实战教程
|
|
34
|
+
- [🛠️ Todo 应用开发](docs/tutorial/todo-app/) - 从零开发完整项目
|
|
35
|
+
|
|
36
|
+
### 参考手册
|
|
37
|
+
- [📖 CLI 命令速查](docs/reference/cli-reference.md)
|
|
38
|
+
- [⚙️ 配置参考](docs/reference/config-reference.md)
|
|
39
|
+
- [🗂️ 问题分类学参考](docs/reference/taxonomy-reference.md)
|
|
40
|
+
|
|
41
|
+
### 其他
|
|
42
|
+
- [❓ 常见问题 (FAQ)](docs/faq.md)
|
|
43
|
+
- [🐛 故障排除](docs/troubleshooting.md)
|
|
23
44
|
|
|
24
45
|
## 快速开始
|
|
25
46
|
|
|
@@ -9,6 +9,7 @@ import click
|
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
from rich.table import Table
|
|
11
11
|
|
|
12
|
+
from mspec.core.openspec_manager import OpenspecManager, OpenspecStatus
|
|
12
13
|
from mspec.utils.console import print_error, print_success, print_warning
|
|
13
14
|
|
|
14
15
|
console = Console()
|
|
@@ -27,6 +28,7 @@ def doctor_cmd() -> None:
|
|
|
27
28
|
|
|
28
29
|
checks = [
|
|
29
30
|
("Python 版本", _check_python_version),
|
|
31
|
+
("openspec CLI", _check_openspec_cli),
|
|
30
32
|
("openspec/ 目录", _check_openspec_dir),
|
|
31
33
|
("config.yaml", _check_config_file),
|
|
32
34
|
("模板文件", _check_template_files),
|
|
@@ -83,6 +85,27 @@ def _check_python_version() -> dict:
|
|
|
83
85
|
return {"ok": True, "message": f"{sys.version.split()[0]}"}
|
|
84
86
|
|
|
85
87
|
|
|
88
|
+
def _check_openspec_cli() -> dict:
|
|
89
|
+
"""检查 openspec CLI 是否安装"""
|
|
90
|
+
manager = OpenspecManager()
|
|
91
|
+
status = manager.check()
|
|
92
|
+
|
|
93
|
+
if status == OpenspecStatus.INSTALLED:
|
|
94
|
+
version = manager.get_version()
|
|
95
|
+
version_str = f" ({version})" if version else ""
|
|
96
|
+
return {"ok": True, "message": f"已安装{version_str}"}
|
|
97
|
+
elif status == OpenspecStatus.NPX_AVAILABLE:
|
|
98
|
+
return {
|
|
99
|
+
"ok": True,
|
|
100
|
+
"message": "未全局安装,但可通过 npx 运行",
|
|
101
|
+
}
|
|
102
|
+
else:
|
|
103
|
+
return {
|
|
104
|
+
"ok": False,
|
|
105
|
+
"message": "未安装。运行 `npm install -g @fission-ai/openspec` 安装",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
86
109
|
def _check_openspec_dir() -> dict:
|
|
87
110
|
"""检查 openspec 目录"""
|
|
88
111
|
if not Path("openspec").exists():
|
|
@@ -12,6 +12,7 @@ from rich.panel import Panel
|
|
|
12
12
|
from rich.table import Table
|
|
13
13
|
|
|
14
14
|
from mspec.core.config import ConfigManager
|
|
15
|
+
from mspec.core.openspec_manager import OpenspecManager, OpenspecStatus
|
|
15
16
|
from mspec.core.template import TemplateManager
|
|
16
17
|
from mspec.utils.console import print_error, print_success
|
|
17
18
|
|
|
@@ -117,27 +118,67 @@ def init_cmd(
|
|
|
117
118
|
console.print(f" [red]• {error}[/red]")
|
|
118
119
|
|
|
119
120
|
|
|
120
|
-
def _run_openspec_init(target_path: Path) ->
|
|
121
|
-
"""调用 openspec init 初始化 OpenSpec 目录结构
|
|
122
|
-
|
|
121
|
+
def _run_openspec_init(target_path: Path) -> bool:
|
|
122
|
+
"""调用 openspec init 初始化 OpenSpec 目录结构
|
|
123
|
+
|
|
124
|
+
支持自动检测 openspec,如未安装则尝试使用 npx 或自动安装
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
target_path: 目标目录路径
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
bool: 是否成功调用 openspec init
|
|
131
|
+
"""
|
|
132
|
+
manager = OpenspecManager()
|
|
133
|
+
|
|
134
|
+
# 1. 检查当前状态
|
|
135
|
+
status = manager.check()
|
|
136
|
+
|
|
137
|
+
if status == OpenspecStatus.INSTALLED:
|
|
138
|
+
console.print("[dim]检测到 openspec 已安装[/dim]")
|
|
139
|
+
elif status == OpenspecStatus.NPX_AVAILABLE:
|
|
140
|
+
console.print("[dim]openspec 未全局安装,将使用 npx 运行[/dim]")
|
|
141
|
+
else:
|
|
142
|
+
# 检查 npm 是否安装
|
|
143
|
+
import shutil
|
|
144
|
+
|
|
145
|
+
if not shutil.which("npm"):
|
|
146
|
+
manager.print_npm_not_found_guide()
|
|
147
|
+
# 询问是否继续(不使用 openspec)
|
|
148
|
+
continue_anyway = questionary.confirm(
|
|
149
|
+
"是否继续初始化(跳过 openspec)?", default=True
|
|
150
|
+
).ask()
|
|
151
|
+
return continue_anyway
|
|
152
|
+
|
|
153
|
+
console.print("[yellow]openspec 未安装,正在尝试自动安装...[/yellow]")
|
|
154
|
+
if manager.install():
|
|
155
|
+
# 重新检测
|
|
156
|
+
status = manager.check()
|
|
157
|
+
else:
|
|
158
|
+
# 安装失败,打印指引
|
|
159
|
+
manager.print_install_guide()
|
|
160
|
+
# 询问是否继续(不使用 openspec)
|
|
161
|
+
continue_anyway = questionary.confirm(
|
|
162
|
+
"是否继续初始化(跳过 openspec)?", default=True
|
|
163
|
+
).ask()
|
|
164
|
+
return continue_anyway
|
|
165
|
+
|
|
166
|
+
# 2. 执行 openspec init
|
|
167
|
+
console.print("[dim]正在调用 openspec init...[/dim]")
|
|
123
168
|
try:
|
|
124
|
-
result =
|
|
125
|
-
["openspec", "init"],
|
|
126
|
-
cwd=str(target_path),
|
|
127
|
-
capture_output=True,
|
|
128
|
-
text=True,
|
|
129
|
-
)
|
|
169
|
+
result = manager.run(["init"], cwd=target_path)
|
|
130
170
|
if result.returncode == 0:
|
|
131
|
-
console.print("[
|
|
171
|
+
console.print("[green]✓ openspec init 完成[/green]")
|
|
172
|
+
return True
|
|
132
173
|
else:
|
|
133
|
-
|
|
134
|
-
f"[yellow]⚠ openspec init
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
174
|
+
if result.stderr:
|
|
175
|
+
console.print(f"[yellow]⚠ openspec init 返回警告: {result.stderr}[/yellow]")
|
|
176
|
+
else:
|
|
177
|
+
console.print("[yellow]⚠ openspec init 返回非零退出码,继续执行...[/yellow]")
|
|
178
|
+
return True # 仍视为成功,继续执行
|
|
179
|
+
except Exception as e:
|
|
180
|
+
console.print(f"[yellow]⚠ openspec init 执行异常: {e}[/yellow]")
|
|
181
|
+
return True # 仍视为成功,继续执行
|
|
141
182
|
|
|
142
183
|
|
|
143
184
|
def _show_dry_run(files: dict, target_path: Path) -> None:
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""openspec 管理模块
|
|
3
|
+
|
|
4
|
+
负责检测、安装和调用 openspec
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import platform
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OpenspecStatus(Enum):
|
|
20
|
+
"""openspec 状态"""
|
|
21
|
+
|
|
22
|
+
INSTALLED = "installed" # 已全局安装
|
|
23
|
+
NPX_AVAILABLE = "npx" # 可通过 npx 运行
|
|
24
|
+
NOT_FOUND = "not_found" # 未安装且无法自动安装
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OpenspecManager:
|
|
28
|
+
"""openspec 管理器
|
|
29
|
+
|
|
30
|
+
负责检测、安装和调用 openspec
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
PACKAGE_NAME = "@fission-ai/openspec"
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
self._status: Optional[OpenspecStatus] = None
|
|
37
|
+
|
|
38
|
+
def check(self) -> OpenspecStatus:
|
|
39
|
+
"""检查 openspec 可用状态
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
OpenspecStatus: 当前状态
|
|
43
|
+
"""
|
|
44
|
+
if self._status is not None:
|
|
45
|
+
return self._status
|
|
46
|
+
|
|
47
|
+
# 1. 检查全局安装
|
|
48
|
+
# Windows 上优先检查 openspec.cmd,因为 npm 会安装 openspec.cmd
|
|
49
|
+
if self._is_windows():
|
|
50
|
+
if shutil.which("openspec.cmd") or shutil.which("openspec"):
|
|
51
|
+
self._status = OpenspecStatus.INSTALLED
|
|
52
|
+
return self._status
|
|
53
|
+
else:
|
|
54
|
+
if shutil.which("openspec"):
|
|
55
|
+
self._status = OpenspecStatus.INSTALLED
|
|
56
|
+
return self._status
|
|
57
|
+
|
|
58
|
+
# 2. 检查 npx 是否可用
|
|
59
|
+
if shutil.which("npx"):
|
|
60
|
+
self._status = OpenspecStatus.NPX_AVAILABLE
|
|
61
|
+
return self._status
|
|
62
|
+
|
|
63
|
+
self._status = OpenspecStatus.NOT_FOUND
|
|
64
|
+
return self._status
|
|
65
|
+
|
|
66
|
+
def _is_windows(self) -> bool:
|
|
67
|
+
"""检查是否为 Windows 系统"""
|
|
68
|
+
return platform.system() == "Windows"
|
|
69
|
+
|
|
70
|
+
def is_available(self) -> bool:
|
|
71
|
+
"""检查是否可用(已安装或可通过 npx 运行)"""
|
|
72
|
+
return self.check() != OpenspecStatus.NOT_FOUND
|
|
73
|
+
|
|
74
|
+
def _get_openspec_cmd(self) -> List[str]:
|
|
75
|
+
"""获取正确的 openspec 命令
|
|
76
|
+
|
|
77
|
+
在 Windows 上,npm 会安装三个文件:
|
|
78
|
+
- openspec (无扩展名的 shell script,Unix 兼容)
|
|
79
|
+
- openspec.cmd (Windows batch 文件)
|
|
80
|
+
- openspec.ps1 (PowerShell 脚本)
|
|
81
|
+
|
|
82
|
+
Windows 无法直接执行无扩展名的 shell script,
|
|
83
|
+
所以需要优先使用 .cmd 版本。
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List[str]: 命令列表,如 ["openspec"] 或 ["openspec.cmd"]
|
|
87
|
+
"""
|
|
88
|
+
# Windows 优先使用 openspec.cmd
|
|
89
|
+
if self._is_windows():
|
|
90
|
+
# 先尝试直接找 openspec.cmd
|
|
91
|
+
cmd_path = shutil.which("openspec.cmd")
|
|
92
|
+
if cmd_path:
|
|
93
|
+
return [cmd_path]
|
|
94
|
+
|
|
95
|
+
# 如果没找到,但找到了 openspec(无扩展名),
|
|
96
|
+
# 尝试在同一目录下找 openspec.cmd
|
|
97
|
+
openspec_path = shutil.which("openspec")
|
|
98
|
+
if openspec_path:
|
|
99
|
+
# 构建 openspec.cmd 的完整路径
|
|
100
|
+
base_path = openspec_path.replace("/openspec", "/openspec.cmd")
|
|
101
|
+
base_path = base_path.replace("\\openspec", "\\openspec.cmd")
|
|
102
|
+
if Path(base_path).exists():
|
|
103
|
+
return [base_path]
|
|
104
|
+
|
|
105
|
+
# 非 Windows 或找不到 .cmd,使用默认命令
|
|
106
|
+
openspec_path = shutil.which("openspec")
|
|
107
|
+
if openspec_path:
|
|
108
|
+
return [openspec_path]
|
|
109
|
+
|
|
110
|
+
return ["openspec"]
|
|
111
|
+
|
|
112
|
+
def run(self, args: list, cwd: Optional[Path] = None) -> subprocess.CompletedProcess:
|
|
113
|
+
"""运行 openspec 命令
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
args: 命令参数
|
|
117
|
+
cwd: 工作目录
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
subprocess.CompletedProcess: 执行结果
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
RuntimeError: 当 openspec 不可用时
|
|
124
|
+
"""
|
|
125
|
+
status = self.check()
|
|
126
|
+
|
|
127
|
+
if status == OpenspecStatus.INSTALLED:
|
|
128
|
+
cmd = self._get_openspec_cmd() + args
|
|
129
|
+
elif status == OpenspecStatus.NPX_AVAILABLE:
|
|
130
|
+
cmd = ["npx", self.PACKAGE_NAME] + args
|
|
131
|
+
else:
|
|
132
|
+
raise RuntimeError("openspec 不可用")
|
|
133
|
+
|
|
134
|
+
return subprocess.run(
|
|
135
|
+
cmd,
|
|
136
|
+
cwd=str(cwd) if cwd else None,
|
|
137
|
+
capture_output=True,
|
|
138
|
+
text=True,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def install(self, global_install: bool = True) -> bool:
|
|
142
|
+
"""安装 openspec
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
global_install: 是否全局安装
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
bool: 是否安装成功
|
|
149
|
+
"""
|
|
150
|
+
if not shutil.which("npm"):
|
|
151
|
+
console.print("[red]✗ npm 未安装,无法自动安装 openspec[/red]")
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
cmd = ["npm", "install"]
|
|
156
|
+
if global_install:
|
|
157
|
+
cmd.append("-g")
|
|
158
|
+
cmd.append(self.PACKAGE_NAME)
|
|
159
|
+
|
|
160
|
+
console.print(f"[dim]正在执行: {' '.join(cmd)}[/dim]")
|
|
161
|
+
|
|
162
|
+
result = subprocess.run(
|
|
163
|
+
cmd,
|
|
164
|
+
capture_output=True,
|
|
165
|
+
text=True,
|
|
166
|
+
timeout=180, # 3分钟超时
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if result.returncode == 0:
|
|
170
|
+
# 清除缓存,重新检测
|
|
171
|
+
self._status = None
|
|
172
|
+
console.print("[green]✓ openspec 安装成功[/green]")
|
|
173
|
+
return True
|
|
174
|
+
else:
|
|
175
|
+
# 检查是否是权限问题
|
|
176
|
+
if "EACCES" in result.stderr or "permission" in result.stderr.lower():
|
|
177
|
+
console.print("[yellow]⚠ 权限不足,无法全局安装[/yellow]")
|
|
178
|
+
else:
|
|
179
|
+
console.print(f"[red]✗ 安装失败: {result.stderr}[/red]")
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
except subprocess.TimeoutExpired:
|
|
183
|
+
console.print("[red]✗ 安装超时[/red]")
|
|
184
|
+
return False
|
|
185
|
+
except Exception as e:
|
|
186
|
+
console.print(f"[red]✗ 安装出错: {e}[/red]")
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
def print_install_guide(self):
|
|
190
|
+
"""打印安装指引"""
|
|
191
|
+
console.print("\n[bold yellow]⚠ openspec 未安装[/bold yellow]")
|
|
192
|
+
console.print("\n请执行以下命令安装:")
|
|
193
|
+
console.print(f" [cyan]npm install -g {self.PACKAGE_NAME}[/cyan]")
|
|
194
|
+
console.print("\n[dim]如果权限不足,请尝试:[/dim]")
|
|
195
|
+
console.print(f" [cyan]sudo npm install -g {self.PACKAGE_NAME}[/cyan]")
|
|
196
|
+
console.print("\n或使用 npx(无需安装):")
|
|
197
|
+
console.print(f" [cyan]npx {self.PACKAGE_NAME} init[/cyan]")
|
|
198
|
+
|
|
199
|
+
def print_npm_not_found_guide(self):
|
|
200
|
+
"""打印 npm 未安装的指引"""
|
|
201
|
+
console.print("\n[bold red]✗ npm 未安装[/bold red]")
|
|
202
|
+
console.print("\n[yellow]openspec 需要 Node.js 环境,请安装 Node.js:[/yellow]")
|
|
203
|
+
console.print(" https://nodejs.org/")
|
|
204
|
+
console.print("\n[dim]安装后请重新运行 mspec init[/dim]")
|
|
205
|
+
|
|
206
|
+
def print_permission_guide(self):
|
|
207
|
+
"""打印权限问题的解决指引"""
|
|
208
|
+
console.print("\n[yellow]⚠ npm 全局安装权限不足[/yellow]")
|
|
209
|
+
console.print("\n解决方案 1 - 使用 npx(推荐,无需安装):")
|
|
210
|
+
console.print(f" [cyan]npx {self.PACKAGE_NAME} init[/cyan]")
|
|
211
|
+
console.print("\n解决方案 2 - 修改 npm 权限:")
|
|
212
|
+
console.print(" https://docs.npmjs.com/resolving-eacces-permissions-errors")
|
|
213
|
+
console.print("\n解决方案 3 - 使用 sudo:")
|
|
214
|
+
console.print(f" [cyan]sudo npm install -g {self.PACKAGE_NAME}[/cyan]")
|
|
215
|
+
|
|
216
|
+
def get_version(self) -> Optional[str]:
|
|
217
|
+
"""获取 openspec 版本
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Optional[str]: 版本号,如果未安装则返回 None
|
|
221
|
+
"""
|
|
222
|
+
if not self.is_available():
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
result = self.run(["--version"])
|
|
227
|
+
if result.returncode == 0:
|
|
228
|
+
return result.stdout.strip()
|
|
229
|
+
except Exception:
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
return None
|
|
@@ -2,16 +2,82 @@ schema: spec-driven
|
|
|
2
2
|
|
|
3
3
|
# =============================================================================
|
|
4
4
|
# OpenSpec 工作流增强配置 v4
|
|
5
|
-
# 交互式命令行澄清机制
|
|
5
|
+
# 交互式命令行澄清机制 - 寄生模式
|
|
6
|
+
# =============================================================================
|
|
7
|
+
# ⚠️ 重要:本配置采用"寄生模式"增强 OpenSpec 核心流程
|
|
8
|
+
# 所有增强逻辑通过 context 和 rules 注入,不改变 OpenSpec schema
|
|
6
9
|
# =============================================================================
|
|
7
10
|
|
|
8
11
|
version: "4.0.0"
|
|
9
12
|
language: zh
|
|
10
13
|
|
|
14
|
+
# =============================================================================
|
|
15
|
+
# 前置条件检查清单(寄生增强)
|
|
16
|
+
# =============================================================================
|
|
17
|
+
#
|
|
18
|
+
# 以下检查项必须在创建对应 artifact 之前完成。
|
|
19
|
+
# 如果检查不通过,禁止继续。
|
|
20
|
+
#
|
|
11
21
|
context: |
|
|
12
|
-
##
|
|
22
|
+
## ⚠️ 强制前置检查清单(不完成禁止继续)
|
|
23
|
+
|
|
24
|
+
### 前置检查 1: 需求澄清
|
|
25
|
+
|
|
26
|
+
**检查项**: `spec/requirement-issues.md` 必须存在且状态为 `clarified`
|
|
27
|
+
|
|
28
|
+
**检查方法**:
|
|
29
|
+
1. 检查文件 `openspec/changes/{change-name}/spec/requirement-issues.md` 是否存在
|
|
30
|
+
2. 检查文件中的状态标记是否为 `clarified`
|
|
31
|
+
|
|
32
|
+
**如果不通过,执行以下流程**:
|
|
33
|
+
```
|
|
34
|
+
步骤 1: 读取 openspec/templates/requirement-issue-taxonomy.md
|
|
35
|
+
步骤 2: 分析需求,识别功能点(Capabilities)
|
|
36
|
+
步骤 3: 按 6 个维度检查每个功能点的问题
|
|
37
|
+
步骤 4: 创建 spec/requirement-issues.md,填充发现问题
|
|
38
|
+
步骤 5: 启动交互式澄清(一问一答)
|
|
39
|
+
- 对 High 和 Medium 问题逐个询问
|
|
40
|
+
- 每题提供 2-4 个选项 + 自定义输入
|
|
41
|
+
- 用户回答后立即同步到 requirement-issues.md
|
|
42
|
+
- 显示进度 "问题 3/10"
|
|
43
|
+
- 支持 'q' 退出保存,'s' 跳过 Low 问题
|
|
44
|
+
步骤 6: 确认所有 High 问题已澄清后,更新状态为 clarified
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**阻塞条件**: requirement-issues.md 不存在 或 状态 ≠ clarified → 禁止创建 proposal
|
|
48
|
+
|
|
49
|
+
### 前置检查 2: 设计澄清
|
|
13
50
|
|
|
14
|
-
|
|
51
|
+
**检查项**: `spec/design-issues.md` 必须存在且状态为 `clarified` 或 `resolved`
|
|
52
|
+
|
|
53
|
+
**执行时机**: AFTER creating design.md, BEFORE creating tasks
|
|
54
|
+
|
|
55
|
+
**如果不通过,执行类似的需求澄清流程**:
|
|
56
|
+
```
|
|
57
|
+
步骤 1: 读取 openspec/templates/design-issue-taxonomy.md
|
|
58
|
+
步骤 2: 按 7 个维度分析 design.md 的不确定性
|
|
59
|
+
步骤 3: 创建 spec/design-issues.md
|
|
60
|
+
步骤 4: 交互式澄清(一问一答)
|
|
61
|
+
步骤 5: 根据澄清结果更新 design.md
|
|
62
|
+
步骤 6: 更新状态为 clarified
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**阻塞条件**: design-issues.md 不存在 或 状态 ∉ {clarified, resolved} → 禁止创建 tasks
|
|
66
|
+
|
|
67
|
+
### 为什么这些检查是强制的?
|
|
68
|
+
|
|
69
|
+
mspec 采用"寄生模式"增强 OpenSpec 核心流程:
|
|
70
|
+
- 不改变 OpenSpec schema(宿主透明)
|
|
71
|
+
- 通过前置条件检查注入增强逻辑
|
|
72
|
+
- 目的是捕获需求/设计中的不确定性,避免返工
|
|
73
|
+
|
|
74
|
+
如果跳过澄清阶段:
|
|
75
|
+
- AI 会基于假设进行实现
|
|
76
|
+
- 假设可能在后续被发现错误
|
|
77
|
+
- 导致代码返工、设计变更
|
|
78
|
+
- 违反"理解先于编码"原则
|
|
79
|
+
|
|
80
|
+
## 📚 问题分类学知识库(用于问题发现)
|
|
15
81
|
|
|
16
82
|
1. **需求问题分类学**: `openspec/templates/requirement-issue-taxonomy.md`
|
|
17
83
|
- 6 个维度:功能完整性、数据相关、用户体验、边界异常、集成依赖、优先级
|
|
@@ -19,14 +85,14 @@ context: |
|
|
|
19
85
|
2. **设计问题分类学**: `openspec/templates/design-issue-taxonomy.md`
|
|
20
86
|
- 7 个维度:架构决策、技术选型、接口设计、数据状态、安全合规、性能可靠性、部署运维
|
|
21
87
|
|
|
22
|
-
|
|
88
|
+
## 🔄 澄清流程状态机
|
|
23
89
|
|
|
24
90
|
```
|
|
25
91
|
需求澄清: discovering → clarifying → clarified → superseded
|
|
26
92
|
设计澄清: analyzing → clarifying → clarified → resolved
|
|
27
93
|
```
|
|
28
94
|
|
|
29
|
-
|
|
95
|
+
## 💬 交互式澄清配置
|
|
30
96
|
|
|
31
97
|
- **交互模式**: 一问一答的引导式交互
|
|
32
98
|
- **界面元素**: 问题描述 + 2-4个备选选项 + 自定义输入
|
|
@@ -38,43 +104,40 @@ context: |
|
|
|
38
104
|
- 输入 'q' 随时退出,进度将自动保存
|
|
39
105
|
- 输入 's' 跳过当前问题(仅 Low 优先级)
|
|
40
106
|
|
|
41
|
-
|
|
107
|
+
## 📋 问题清单模板位置
|
|
42
108
|
|
|
43
109
|
- 功能点问题清单: `openspec/templates/requirement-issues.md`
|
|
44
110
|
- 设计问题清单: `openspec/templates/design-issues.md`
|
|
45
111
|
|
|
46
|
-
|
|
112
|
+
## ⚠️ 问题严重程度定义
|
|
47
113
|
|
|
48
114
|
- 🔴 High: 阻塞性问题,必须澄清后才能继续
|
|
49
115
|
- 🟡 Medium: 重要问题,建议澄清
|
|
50
116
|
- 🟢 Low: 建议性问题,可后续补充
|
|
51
117
|
|
|
118
|
+
# =============================================================================
|
|
119
|
+
# 阶段规则(与前置检查配合使用)
|
|
120
|
+
# =============================================================================
|
|
121
|
+
|
|
52
122
|
rules:
|
|
53
123
|
# ===========================================================================
|
|
54
124
|
# 阶段 1: 需求澄清 (在 proposal 之前)
|
|
55
125
|
# ===========================================================================
|
|
56
126
|
proposal:
|
|
57
|
-
#
|
|
127
|
+
# 强制前置检查
|
|
58
128
|
- |
|
|
129
|
+
⚠️ 强制性前置检查(MUST 执行):
|
|
130
|
+
|
|
59
131
|
BEFORE creating proposal:
|
|
60
|
-
1.
|
|
61
|
-
2.
|
|
62
|
-
3.
|
|
63
|
-
4.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- 展示 High 和 Medium 问题,一问一答
|
|
70
|
-
- 每个问题提供 2-4 个预设选项 + 自定义输入
|
|
71
|
-
- 用户选择/输入后确认,立即同步到问题清单
|
|
72
|
-
- 显示进度 "问题 3/10"
|
|
73
|
-
- 支持 'q' 退出保存,下次自动恢复;'s' 跳过 Low 问题
|
|
74
|
-
6. 迭代检查:澄清后是否暴露新问题?
|
|
75
|
-
- 如果有新问题,更新清单并继续澄清
|
|
76
|
-
- 如果没有新问题,将状态设为 clarified
|
|
77
|
-
7. 只有在 requirement-issues.md 状态为 clarified 后才创建 proposal
|
|
132
|
+
1. MUST 检查 spec/requirement-issues.md 是否存在
|
|
133
|
+
2. MUST 检查状态是否为 clarified
|
|
134
|
+
3. 如果不满足以上条件,MUST 先执行前置检查 1: 需求澄清流程
|
|
135
|
+
4. ONLY THEN 创建 proposal
|
|
136
|
+
|
|
137
|
+
检查失败时的处理:
|
|
138
|
+
- 停止 artifacts 创建流程
|
|
139
|
+
- 启动交互式需求澄清
|
|
140
|
+
- 等待澄清完成后再继续
|
|
78
141
|
|
|
79
142
|
# 引用澄清结果
|
|
80
143
|
- |
|
|
@@ -84,10 +147,10 @@ rules:
|
|
|
84
147
|
- 在 "Capabilities" 部分确保与功能点问题清单中的功能点 ID 一致
|
|
85
148
|
- 在 "Impact" 部分体现澄清后的约束和依赖
|
|
86
149
|
|
|
87
|
-
#
|
|
88
|
-
- "
|
|
89
|
-
- "
|
|
90
|
-
- "
|
|
150
|
+
# 质量门禁
|
|
151
|
+
- "🚫 禁止跳过: 如果 requirement-issues.md 不存在,禁止创建 proposal"
|
|
152
|
+
- "🚫 禁止跳过: 如果状态不是 clarified,禁止创建 proposal"
|
|
153
|
+
- "✅ 必须引用: proposal 中必须提及需求澄清中的关键决策"
|
|
91
154
|
|
|
92
155
|
# ===========================================================================
|
|
93
156
|
# 阶段 2: 需求规格 (specs)
|
|
@@ -114,45 +177,33 @@ rules:
|
|
|
114
177
|
- 为什么放弃其他方案
|
|
115
178
|
3. 特别注意 requirement-issues.md 中澄清的约束条件
|
|
116
179
|
|
|
117
|
-
#
|
|
180
|
+
# 后置强制检查
|
|
118
181
|
- |
|
|
182
|
+
⚠️ 强制性后置检查(MUST 执行):
|
|
183
|
+
|
|
119
184
|
AFTER creating design.md:
|
|
120
|
-
1.
|
|
121
|
-
2.
|
|
122
|
-
3.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
- 每个问题提供 2-4 个预设选项 + 自定义输入
|
|
130
|
-
- 用户选择/输入后确认,立即同步到问题清单
|
|
131
|
-
- 显示进度 "问题 3/10"
|
|
132
|
-
- 支持 'q' 退出保存,下次自动恢复;'s' 跳过 Low 问题
|
|
133
|
-
5. 根据澄清结果更新 design.md
|
|
134
|
-
- 修改设计方案以反映澄清后的决策
|
|
135
|
-
- 在 design.md 中记录设计变更历史
|
|
136
|
-
6. 迭代检查:设计变更是否引入新问题?
|
|
137
|
-
- 如果有新问题,更新 design-issues.md 并继续澄清
|
|
138
|
-
- 如果没有新问题,将状态设为 clarified
|
|
139
|
-
7. 只有在 design-issues.md 状态为 clarified 或 resolved 后才创建 tasks
|
|
185
|
+
1. MUST 检查 spec/design-issues.md 是否存在
|
|
186
|
+
2. MUST 检查状态是否为 clarified 或 resolved
|
|
187
|
+
3. 如果不满足以上条件,MUST 先执行前置检查 2: 设计澄清流程
|
|
188
|
+
4. 根据澄清结果更新 design.md
|
|
189
|
+
5. ONLY THEN 创建 tasks
|
|
190
|
+
|
|
191
|
+
# 质量门禁
|
|
192
|
+
- "🚫 禁止跳过: 如果 design-issues.md 不存在,禁止创建 tasks"
|
|
193
|
+
- "🚫 禁止跳过: 如果状态不是 clarified/resloved,禁止创建 tasks"
|
|
140
194
|
|
|
141
195
|
# ===========================================================================
|
|
142
196
|
# 阶段 4: 实施任务 (tasks)
|
|
143
197
|
# ===========================================================================
|
|
144
198
|
tasks:
|
|
145
|
-
#
|
|
199
|
+
# 前置检查(冗余声明,确保不被遗漏)
|
|
146
200
|
- |
|
|
201
|
+
⚠️ 前置检查(再次确认):
|
|
147
202
|
BEFORE creating tasks:
|
|
148
|
-
1. 检查 spec/
|
|
149
|
-
2.
|
|
150
|
-
3.
|
|
151
|
-
|
|
152
|
-
- 触发设计问题发现流程
|
|
153
|
-
- 等待澄清完成后再继续
|
|
154
|
-
|
|
155
|
-
# 任务内容
|
|
203
|
+
1. 检查 spec/requirement-issues.md 状态为 clarified ✓
|
|
204
|
+
2. 检查 spec/design-issues.md 状态为 clarified 或 resolved ✓
|
|
205
|
+
3. 两个检查都通过后才能创建 tasks
|
|
206
|
+
|
|
156
207
|
- |
|
|
157
208
|
创建 tasks 时:
|
|
158
209
|
1. 基于最终确定的技术方案创建任务
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mspec-cli
|
|
3
|
-
Version: 4.0.
|
|
3
|
+
Version: 4.0.5
|
|
4
4
|
Summary: mspec-cli - 成员规范驱动开发工作流 CLI 工具
|
|
5
5
|
Author-email: shirayner <team@mspec.io>
|
|
6
6
|
License: MIT
|
|
@@ -57,8 +57,29 @@ Dynamic: license-file
|
|
|
57
57
|
|
|
58
58
|
## 文档
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
📚 **[完整文档 →](docs/)**
|
|
61
|
+
|
|
62
|
+
### 新手入门
|
|
63
|
+
- [📖 安装指南](docs/getting-started/installation.md) - 系统要求与安装步骤
|
|
64
|
+
- [⚡ 5分钟快速开始](docs/getting-started/quickstart.md) - 体验完整流程
|
|
65
|
+
- [🎯 第一个完整变更](docs/getting-started/first-change.md) - 从零到交付的详细教程
|
|
66
|
+
|
|
67
|
+
### 完整指南
|
|
68
|
+
- [💡 核心概念](docs/guide/concepts.md) - Change、Capability、问题分类学
|
|
69
|
+
- [🔧 CLI 命令详解](docs/guide/cli-commands.md) - init、update、doctor
|
|
70
|
+
- [📋 工作流详解](docs/guide/) - 探索 → 澄清 → 提案 → 设计 → 实施 → 归档
|
|
71
|
+
|
|
72
|
+
### 实战教程
|
|
73
|
+
- [🛠️ Todo 应用开发](docs/tutorial/todo-app/) - 从零开发完整项目
|
|
74
|
+
|
|
75
|
+
### 参考手册
|
|
76
|
+
- [📖 CLI 命令速查](docs/reference/cli-reference.md)
|
|
77
|
+
- [⚙️ 配置参考](docs/reference/config-reference.md)
|
|
78
|
+
- [🗂️ 问题分类学参考](docs/reference/taxonomy-reference.md)
|
|
79
|
+
|
|
80
|
+
### 其他
|
|
81
|
+
- [❓ 常见问题 (FAQ)](docs/faq.md)
|
|
82
|
+
- [🐛 故障排除](docs/troubleshooting.md)
|
|
62
83
|
|
|
63
84
|
## 快速开始
|
|
64
85
|
|
|
@@ -11,6 +11,7 @@ src/mspec/commands/init.py
|
|
|
11
11
|
src/mspec/commands/update.py
|
|
12
12
|
src/mspec/core/__init__.py
|
|
13
13
|
src/mspec/core/config.py
|
|
14
|
+
src/mspec/core/openspec_manager.py
|
|
14
15
|
src/mspec/core/template.py
|
|
15
16
|
src/mspec/templates/config.yaml
|
|
16
17
|
src/mspec/templates/design-issue-taxonomy.md
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|