mumuspec 0.1.0
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.
- package/README.md +83 -0
- package/bin/create-mumuspec.js +2 -0
- package/bin/mumuspec.js +43 -0
- package/package.json +14 -0
- package/src/commands/anchor.js +43 -0
- package/src/commands/close.js +45 -0
- package/src/commands/console.js +49 -0
- package/src/commands/create.js +58 -0
- package/src/commands/design.js +47 -0
- package/src/commands/elicit.js +49 -0
- package/src/commands/freeze.js +28 -0
- package/src/commands/impact.js +43 -0
- package/src/commands/propose.js +41 -0
- package/src/commands/status.js +34 -0
- package/src/commands/sync.js +21 -0
- package/src/commands/validate.js +52 -0
- package/src/commands/view.js +38 -0
- package/src/templates.js +16 -0
- package/src/utils.js +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# MumuSpec CLI
|
|
2
|
+
|
|
3
|
+
AI Coding 企业 Spec 驱动开发工具链。一条命令生成 Spec、校验 Spec、联动 Spec。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g mumuspec
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
或直接用 npx:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx mumuspec create my-project
|
|
15
|
+
npx create-mumuspec my-project
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 命令
|
|
19
|
+
|
|
20
|
+
### 项目初始化
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
mumuspec create <name> # 生成 Spec 项目骨架(14 份文档 + AGENTS.md)
|
|
24
|
+
mumuspec console [dir] # 生成 Spec 可视化控制台 HTML
|
|
25
|
+
mumuspec status [dir] # 查看项目整体状态
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Spec 内容生成 — 五轮工作流
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
mumuspec elicit # R1·采集 — 原始素材 → 02 需求采集
|
|
32
|
+
mumuspec propose # R2·提案 — 03 立项提案 + 04 PRD
|
|
33
|
+
mumuspec anchor # R3·锚定 — 05 用户故事 ↔ 09 API 契约
|
|
34
|
+
mumuspec design # R4·设计 — 06 FSD + 07 NFR + 08+10+11
|
|
35
|
+
mumuspec close # R5·收口 — 12 实施计划 + 13 测试策略 + 14 RTM
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 维护与校验
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
mumuspec validate # 门禁校验 — AC/TC 追溯、字段完整性、文档一致性
|
|
42
|
+
mumuspec impact <spec-id> # 变更影响分析 — 改这份会波及哪些文档
|
|
43
|
+
mumuspec sync <spec-id> # 变更联动 — 自动更新关联文档
|
|
44
|
+
mumuspec freeze # 版本冻结 — 打 Git tag + 锁定 Spec
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 角色视图
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
mumuspec view --role pm # 产品经理视角 (02-06)
|
|
51
|
+
mumuspec view --role dev # 开发者视角 (05-06-09-10-12)
|
|
52
|
+
mumuspec view --role qa # QA 视角 (05-06-09-13-14)
|
|
53
|
+
mumuspec view --role arch # 架构师视角 (01-07-08-11)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 典型工作流
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 1. 创建项目
|
|
60
|
+
mumuspec create ecommerce-promo
|
|
61
|
+
cd ecommerce-promo
|
|
62
|
+
|
|
63
|
+
# 2. 按五轮工作流生成 Spec(每轮将 Prompt 复制到 AI 工具执行)
|
|
64
|
+
mumuspec elicit # → AI 生成 02
|
|
65
|
+
mumuspec propose # → AI 生成 03-04
|
|
66
|
+
mumuspec anchor # → AI 生成 05、09
|
|
67
|
+
mumuspec design # → AI 生成 06-07-08-10-11
|
|
68
|
+
mumuspec close # → AI 生成 12-13-14
|
|
69
|
+
|
|
70
|
+
# 3. 校验
|
|
71
|
+
mumuspec validate
|
|
72
|
+
|
|
73
|
+
# 4. 可视化
|
|
74
|
+
mumuspec console
|
|
75
|
+
|
|
76
|
+
# 5. 冻结
|
|
77
|
+
mumuspec freeze
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 更多
|
|
81
|
+
|
|
82
|
+
- [mumucoding.com](https://mumucoding.com)
|
|
83
|
+
- [GitHub](https://github.com/lvzhaobo/mumu-coding)
|
package/bin/mumuspec.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const cmd = process.argv[2];
|
|
3
|
+
|
|
4
|
+
const HELP = `
|
|
5
|
+
MumuSpec — AI Coding 企业 Spec 驱动开发工具链
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
mumuspec <命令> [参数]
|
|
9
|
+
|
|
10
|
+
项目初始化:
|
|
11
|
+
create <name> 生成 Spec 项目骨架(14 份文档 + AGENTS.md)
|
|
12
|
+
console [dir] [out] 生成 Spec 可视化控制台 HTML
|
|
13
|
+
status [dir] 查看 Spec 项目状态
|
|
14
|
+
|
|
15
|
+
Spec 内容生成(四轮工作流):
|
|
16
|
+
elicit [dir] R1·采集 — 原始素材 → 02 需求采集
|
|
17
|
+
propose [dir] R2·提案 — 03 立项提案 + 04 PRD
|
|
18
|
+
anchor [dir] R3·锚定 — 05 用户故事 ↔ 09 API 契约
|
|
19
|
+
design [dir] R4·设计 — 06 FSD + 07 NFR + 08+10+11
|
|
20
|
+
close [dir] R5·收口 — 12 实施计划 + 13 测试策略 + 14 RTM
|
|
21
|
+
|
|
22
|
+
维护与校验:
|
|
23
|
+
validate [dir] 门禁校验 — AC/TC 追溯、字段完整性、冻结状态
|
|
24
|
+
impact <spec-id> [dir] 变更影响分析 — 改这份会波及哪些文档
|
|
25
|
+
sync <spec-id> [dir] 变更联动 — 更新关联文档
|
|
26
|
+
freeze [dir] 版本冻结 — 打 Git tag + 锁定 Spec
|
|
27
|
+
|
|
28
|
+
角色视图:
|
|
29
|
+
view --role <role> [dir] 按角色查看 Spec(pm|dev|qa|arch)
|
|
30
|
+
|
|
31
|
+
示例:
|
|
32
|
+
mumuspec create my-project
|
|
33
|
+
mumuspec propose
|
|
34
|
+
mumuspec validate
|
|
35
|
+
mumuspec view --role dev
|
|
36
|
+
`;
|
|
37
|
+
const CMDS = ['create','console','status','elicit','propose','anchor','design','close','validate','impact','sync','freeze','view','lint'];
|
|
38
|
+
|
|
39
|
+
if (!cmd || cmd === '--help' || cmd === '-h') { console.log(HELP); process.exit(0); }
|
|
40
|
+
if (cmd === '--version' || cmd === '-v') { console.log('mumuspec v'+require('../package.json').version); process.exit(0); }
|
|
41
|
+
if (!CMDS.includes(cmd)) { console.log(`未知命令: ${cmd}\n${HELP}`); process.exit(1); }
|
|
42
|
+
|
|
43
|
+
require(`../src/commands/${cmd}`)(process.argv.slice(3));
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mumuspec",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MumuSpec CLI — AI Coding 企业 Spec 驱动开发工具链。一条命令生成 Spec、校验 Spec、联动 Spec。",
|
|
5
|
+
"keywords": ["spec", "ai-coding", "spec-driven", "cli", "mumuspec"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "MumuLab",
|
|
8
|
+
"repository": "https://github.com/lvzhaobo/mumu-coding",
|
|
9
|
+
"bin": {
|
|
10
|
+
"mumuspec": "./bin/mumuspec.js",
|
|
11
|
+
"create-mumuspec": "./bin/create-mumuspec.js"
|
|
12
|
+
},
|
|
13
|
+
"engines": { "node": ">=16" }
|
|
14
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// R3·锚定 — 05 用户故事 ↔ 09 API 契约 双向对齐
|
|
2
|
+
const { resolveDir, readSpec } = require('../utils');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
module.exports = function(args) {
|
|
6
|
+
const dir = resolveDir(args[0]);
|
|
7
|
+
const s04 = readSpec(dir, 4);
|
|
8
|
+
|
|
9
|
+
console.log('📋 R3·锚定 — 生成 05 用户故事 + 09 API 契约(双向对齐)');
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log('输入: 04 PRD');
|
|
12
|
+
console.log('输出: ' + path.join(dir, '05-user-stories.md'));
|
|
13
|
+
console.log(' ' + path.join(dir, '09-api-spec.md'));
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log('请将以下 Prompt 复制到 AI 编码工具中执行:');
|
|
16
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
17
|
+
console.log(`你是需求分析 + 接口设计专家。请根据 04-PRD,同时生成 05-用户故事 和 09-API契约,确保两者字段级对齐。
|
|
18
|
+
|
|
19
|
+
## 第一步:生成 05-用户故事
|
|
20
|
+
- 按角色拆分用户故事(作为[角色],我想要[功能],以便[价值])
|
|
21
|
+
- 每个故事附带 AC(验收条件)
|
|
22
|
+
- 用 MoSCoW 排序(Must/Should/Could/Won't)
|
|
23
|
+
- 标注故事间的依赖关系
|
|
24
|
+
|
|
25
|
+
## 第二步:生成 09-API契约
|
|
26
|
+
- 为每个用户故事推导 API 端点
|
|
27
|
+
- 列出每个端点的请求/响应格式(JSON)
|
|
28
|
+
- 错误码和错误响应格式
|
|
29
|
+
- 标注每个端点关联的用户故事和 AC
|
|
30
|
+
|
|
31
|
+
## 关键约束:双向对齐
|
|
32
|
+
- 05 中的每个 AC → 09 中必须有对应的 API 端点或字段
|
|
33
|
+
- 09 中的每个端点 → 05 中必须有对应的用户故事
|
|
34
|
+
- AC 中的字段名 = API 中的字段名(不允许不一致)
|
|
35
|
+
|
|
36
|
+
格式:中文描述 + JSON 示例,Markdown 表格
|
|
37
|
+
|
|
38
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
39
|
+
if (s04) { console.log('📎 04-PRD(已加载): ' + s04.length + ' 字符'); }
|
|
40
|
+
else { console.log('⚠️ 04-PRD 内容为空。建议先运行 mumuspec propose'); }
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log('💡 生成后保存为 05-user-stories.md 和 09-api-spec.md');
|
|
43
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// R5·收口 — 12 实施计划 + 13 测试策略 + 14 RTM
|
|
2
|
+
const { resolveDir, readSpec } = require('../utils');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
module.exports = function(args) {
|
|
6
|
+
const dir = resolveDir(args[0]);
|
|
7
|
+
|
|
8
|
+
console.log('📋 R5·收口 — 生成 12 实施计划 + 13 测试策略 + 14 追溯矩阵');
|
|
9
|
+
console.log('');
|
|
10
|
+
console.log('输入: 全部已完成 Spec (01-11)');
|
|
11
|
+
console.log('输出:');
|
|
12
|
+
console.log(' ' + path.join(dir, '12-implementation-plan.md'));
|
|
13
|
+
console.log(' ' + path.join(dir, '13-test-strategy.md'));
|
|
14
|
+
console.log(' ' + path.join(dir, '14-traceability-matrix.md'));
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log('请将以下 Prompt 复制到 AI 编码工具中执行:');
|
|
17
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
18
|
+
console.log(`你是项目经理 + QA 专家。请根据项目全部 Spec 文档,生成以下三份收口文档:
|
|
19
|
+
|
|
20
|
+
## 12-实施计划
|
|
21
|
+
里程碑(含交付物和验收标准)、WBS 任务分解(含负责人和预估工时)、依赖关系、风险矩阵、回滚方案
|
|
22
|
+
|
|
23
|
+
## 13-测试策略
|
|
24
|
+
五层测试策略(单元/集成/E2E/性能/安全)、测试范围、通过标准(准入+准出)、自动化策略、追溯(每个测试用例关联 AC 和 API)
|
|
25
|
+
|
|
26
|
+
## 关键:14-RTM 追溯矩阵
|
|
27
|
+
遍历全部已写 Spec 文档:
|
|
28
|
+
- 从 04-PRD 提取功能列表
|
|
29
|
+
- 从 05-用户故事 提取 US 编号和 AC 编号
|
|
30
|
+
- 从 06-FSD 提取对应的章节
|
|
31
|
+
- 从 09-API 提取对应的端点
|
|
32
|
+
- 从 10-数据模型 提取对应的表
|
|
33
|
+
- 从 13-测试策略 提取对应的测试用例
|
|
34
|
+
- 填满一条完整的追溯链:需求→PRD→US→FSD→API→数据→测试
|
|
35
|
+
- 统计覆盖率,标注缺口
|
|
36
|
+
|
|
37
|
+
格式:中文 + Markdown 巨型表格
|
|
38
|
+
|
|
39
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
40
|
+
console.log('');
|
|
41
|
+
const counts = [];
|
|
42
|
+
for (let i = 3; i <= 11; i++) { const c = readSpec(dir, i); if (c) counts.push('0'+i+': '+c.length+' 字符'); }
|
|
43
|
+
if (counts.length) console.log('📎 已加载文档: ' + counts.join(', '));
|
|
44
|
+
console.log('💡 建议先生成 12+13,最后生成 14(需要遍历全部文档)');
|
|
45
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// mumuspec console — 生成 Spec 可视化控制台 HTML
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { resolveDir, readSpec } = require('../utils');
|
|
5
|
+
|
|
6
|
+
module.exports = function(args) {
|
|
7
|
+
const specDir = resolveDir(args[0] || 'specs');
|
|
8
|
+
const outputPath = path.resolve(args[1] || 'spec-console.html');
|
|
9
|
+
|
|
10
|
+
if (!fs.existsSync(specDir)) { console.error('❌ 目录不存在: ' + specDir); process.exit(1); }
|
|
11
|
+
|
|
12
|
+
const docs = [];
|
|
13
|
+
for (let i = 1; i <= 14; i++) {
|
|
14
|
+
const content = readSpec(specDir, i);
|
|
15
|
+
const titleMatch = content ? content.match(/^#\s*\d*\s*[—\-−]\s*(.+)/m) : null;
|
|
16
|
+
docs.push({ num: i, title: titleMatch ? titleMatch[1].trim() : '—', status: content ? (content.replace(/#.*\n?/g,'').replace(/>.*\n?/g,'').trim().length > 50 ? 'filled' : 'empty') : 'missing' });
|
|
17
|
+
}
|
|
18
|
+
const filled = docs.filter(d => d.status === 'filled').length;
|
|
19
|
+
|
|
20
|
+
const html = `<!DOCTYPE html><html lang="zh-CN">
|
|
21
|
+
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>Spec Console — ${path.basename(path.resolve(specDir))}</title>
|
|
22
|
+
<style>
|
|
23
|
+
*{margin:0;padding:0;box-sizing:border-box}body{font-family:"PingFang SC",-apple-system,sans-serif;color:#1a1a1a;background:#f8f8f5;line-height:1.6}
|
|
24
|
+
.topbar{position:sticky;top:0;z-index:10;background:rgba(248,248,245,0.88);backdrop-filter:blur(10px);border-bottom:1px solid #e4e4e0;padding:14px 32px;display:flex;align-items:center;justify-content:space-between}
|
|
25
|
+
.topbar h1{font-size:16px;font-weight:650;color:#111}.topbar .badge{font-size:12px;padding:4px 10px;border-radius:12px;background:#e8f5e8;color:#3a8a3a}
|
|
26
|
+
.page{max-width:1200px;margin:0 auto;padding:32px}
|
|
27
|
+
.progress-wrap{margin-bottom:32px;background:#fff;border:1px solid #e4e4e0;border-radius:8px;padding:20px 24px;display:flex;align-items:center;gap:20px}
|
|
28
|
+
.progress-bar{flex:1;height:8px;background:#f0f0ed;border-radius:4px;overflow:hidden}.progress-fill{height:100%;background:#d94f4f;border-radius:4px;transition:width .5s}
|
|
29
|
+
.progress-text{font-size:14px;font-weight:650;color:#111;white-space:nowrap}
|
|
30
|
+
.doc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:2px;background:#e4e4e0;border:1px solid #e4e4e0;border-radius:8px;overflow:hidden}
|
|
31
|
+
.doc-card{background:#fff;padding:18px 20px}.doc-card h3{font-size:13px;font-weight:650;color:#111;margin-bottom:4px}
|
|
32
|
+
.dc-status{font-size:10px;padding:2px 6px;border-radius:3px;font-weight:600;display:inline-block;margin-bottom:6px}
|
|
33
|
+
.dc-status.filled{background:#e8f5e8;color:#3a8a3a}.dc-status.empty{background:#fef3f3;color:#d94f4f}.dc-status.missing{background:#f5f5f5;color:#ccc}
|
|
34
|
+
.footer{text-align:center;padding:40px 0;border-top:1px solid #e4e4e0;margin-top:32px}.footer p{font-size:11px;color:#ccc}
|
|
35
|
+
@media(max-width:768px){.doc-grid{grid-template-columns:1fr}}
|
|
36
|
+
</style></head>
|
|
37
|
+
<body>
|
|
38
|
+
<nav class="topbar"><h1>Spec Console</h1><span class="badge">${filled}/14 份已填写</span></nav>
|
|
39
|
+
<div class="page">
|
|
40
|
+
<div class="progress-wrap"><div class="progress-bar"><div class="progress-fill" style="width:${Math.round(filled/14*100)}%"></div></div><div class="progress-text">${filled}/14</div></div>
|
|
41
|
+
<div class="doc-grid">
|
|
42
|
+
${docs.map(d => `<div class="doc-card"><h3>${String(d.num).padStart(2,'0')} — ${d.title}</h3><span class="dc-status ${d.status}">${d.status==='filled'?'✅ 已填写':d.status==='empty'?'⚠️ 内容空':'❌ 文件缺失'}</span></div>`).join('')}
|
|
43
|
+
</div></div>
|
|
44
|
+
<footer class="footer"><p>Generated by MumuSpec CLI · <a href="https://mumucoding.com" style="color:#aaa">mumucoding.com</a></p></footer>
|
|
45
|
+
</body></html>`;
|
|
46
|
+
fs.writeFileSync(outputPath, html, 'utf-8');
|
|
47
|
+
console.log('✨ Spec Console 已生成: ' + outputPath);
|
|
48
|
+
console.log('📊 ' + filled + '/14 份已填写');
|
|
49
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// mumuspec create — 生成 Spec 项目骨架
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const TEMPLATES = require('../templates');
|
|
6
|
+
|
|
7
|
+
module.exports = function(args) {
|
|
8
|
+
const name = args[0];
|
|
9
|
+
if (!name) { console.log('用法: mumuspec create <项目名>\n示例: mumuspec create my-project'); process.exit(1); }
|
|
10
|
+
|
|
11
|
+
const root = path.resolve(name);
|
|
12
|
+
if (fs.existsSync(root)) { console.error('❌ 目录已存在: ' + name); process.exit(1); }
|
|
13
|
+
|
|
14
|
+
const specsDir = path.join(root, 'specs');
|
|
15
|
+
fs.mkdirSync(specsDir, { recursive: true });
|
|
16
|
+
|
|
17
|
+
let count = 0;
|
|
18
|
+
Object.entries(TEMPLATES).forEach(([filename, content]) => {
|
|
19
|
+
fs.writeFileSync(path.join(specsDir, filename), content.trim() + '\n', 'utf-8'); count++;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const agentsMd = `# AGENTS.md — AI 编码助手角色定义
|
|
23
|
+
|
|
24
|
+
## 角色
|
|
25
|
+
你是本项目的 AI 编程助手。你的唯一输入是本项目的 Spec 文档(specs/ 目录)。
|
|
26
|
+
|
|
27
|
+
## 工作流程
|
|
28
|
+
1. 接到任务 → 先读取相关的 Spec 文档(03-13)
|
|
29
|
+
2. 理解需求 → 基于 Spec 中的 AC 确认验收标准
|
|
30
|
+
3. 生成代码 → 严格按 Spec 定义的 API 契约和数据模型
|
|
31
|
+
4. 自检 → 生成后对照 Spec 逐条验证 AC
|
|
32
|
+
5. 提交 PR → PR 描述中关联 Spec 编号
|
|
33
|
+
|
|
34
|
+
## 行为边界
|
|
35
|
+
- ✅ 可以:读写 src/ 目录下的代码文件
|
|
36
|
+
- ✅ 可以:运行测试和 Lint
|
|
37
|
+
- ❌ 禁止:直接修改 main/master 分支
|
|
38
|
+
- ❌ 禁止:删除未 git tracked 的文件
|
|
39
|
+
- ❌ 禁止:执行需要人工确认的系统命令`;
|
|
40
|
+
fs.writeFileSync(path.join(root, 'AGENTS.md'), agentsMd.trim() + '\n', 'utf-8');
|
|
41
|
+
|
|
42
|
+
fs.writeFileSync(path.join(root, '.gitignore'), 'node_modules/\n.env\n.DS_Store\nspecs/*.local.md\n', 'utf-8');
|
|
43
|
+
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log('✨ MumuSpec 项目已创建: ' + name + '/');
|
|
46
|
+
console.log(' specs/ — ' + count + ' 份 Spec 模板 (01-14)');
|
|
47
|
+
console.log(' AGENTS.md — AI 编码助手角色定义');
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log('🚀 下一步:');
|
|
50
|
+
console.log(' cd ' + name);
|
|
51
|
+
console.log(' mumuspec elicit # R1: 需求采集');
|
|
52
|
+
console.log(' mumuspec propose # R2: 立项提案');
|
|
53
|
+
console.log(' mumuspec anchor # R3: 锚定对齐');
|
|
54
|
+
console.log(' mumuspec design # R4: 设计生成');
|
|
55
|
+
console.log(' mumuspec close # R5: 收口');
|
|
56
|
+
console.log(' mumuspec validate # 门禁校验');
|
|
57
|
+
console.log('');
|
|
58
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// R4·设计 — 06 FSD + 07 NFR + 08 架构 + 10 数据 + 11 安全
|
|
2
|
+
const { resolveDir, readSpec } = require('../utils');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
module.exports = function(args) {
|
|
6
|
+
const dir = resolveDir(args[0]);
|
|
7
|
+
const s05 = readSpec(dir, 5);
|
|
8
|
+
const s09 = readSpec(dir, 9);
|
|
9
|
+
|
|
10
|
+
console.log('📋 R4·设计 — 生成功能规格 + 非功能需求 + 架构 + 数据 + 安全');
|
|
11
|
+
console.log('');
|
|
12
|
+
console.log('输入: 05 用户故事 + 09 API 契约');
|
|
13
|
+
console.log('输出:');
|
|
14
|
+
console.log(' ' + path.join(dir, '06-fsd.md'));
|
|
15
|
+
console.log(' ' + path.join(dir, '07-nfr.md'));
|
|
16
|
+
console.log(' ' + path.join(dir, '08-architecture.md'));
|
|
17
|
+
console.log(' ' + path.join(dir, '10-data-model.md'));
|
|
18
|
+
console.log(' ' + path.join(dir, '11-security.md'));
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log('请将以下 Prompt 复制到 AI 编码工具中执行:');
|
|
21
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
22
|
+
console.log(`你是全栈技术专家。请根据 05-用户故事 和 09-API契约,生成以下五份文档:
|
|
23
|
+
|
|
24
|
+
## 06-FSD 功能规格
|
|
25
|
+
每个功能:交互流程(步骤级)、界面说明(布局+组件)、异常处理(场景+提示)、状态机(如有)、边界条件
|
|
26
|
+
|
|
27
|
+
## 07-NFR 非功能需求
|
|
28
|
+
性能(响应时间/吞吐量/并发)、可用性(SLA/容灾)、安全(认证/授权)、兼容性、可维护性
|
|
29
|
+
|
|
30
|
+
## 08-架构选型
|
|
31
|
+
技术栈选型对比、ADR(架构决策记录)、部署架构、依赖关系
|
|
32
|
+
|
|
33
|
+
## 10-数据模型
|
|
34
|
+
ER 图(Mermaid)、表结构(字段/类型/约束)、索引策略、数据流
|
|
35
|
+
|
|
36
|
+
## 11-安全设计
|
|
37
|
+
攻击面分析、权限模型、数据加密、安全控制措施、审计日志、合规映射
|
|
38
|
+
|
|
39
|
+
格式:中文 + Mermaid 图 + Markdown 表格。所有技术决策要写理由。
|
|
40
|
+
|
|
41
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
42
|
+
if (s05) console.log('📎 05-用户故事: ' + s05.length + ' 字符');
|
|
43
|
+
if (s09) console.log('📎 09-API契约: ' + s09.length + ' 字符');
|
|
44
|
+
if (!s05 && !s09) console.log('⚠️ 输入文档为空。建议先运行 mumuspec anchor');
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log('💡 建议分步生成:先 06+07,再 08+10,最后 11');
|
|
47
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// mumuspec elicit — R1 采集:原始素材 → 02 需求采集
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { resolveDir } = require('../utils');
|
|
5
|
+
|
|
6
|
+
const PROMPT = `你是需求分析专家。请根据以下原始素材,生成 02-需求采集文档。
|
|
7
|
+
|
|
8
|
+
生成的文档应包含以下章节:
|
|
9
|
+
## 1. 需求来源 — 列出需求的来源方、类型和优先级
|
|
10
|
+
## 2. 干系人清单 — 每个干系人的角色、核心诉求、决策权限
|
|
11
|
+
## 3. 业务背景 — 为什么这个需求重要,不做的后果
|
|
12
|
+
## 4. 初步假设 — 当前对需求的假设,需后续验证
|
|
13
|
+
|
|
14
|
+
格式要求:
|
|
15
|
+
- 用中文撰写
|
|
16
|
+
- 表格用 Markdown 表格格式
|
|
17
|
+
- 不确定的内容标注 [待确认]
|
|
18
|
+
- 不要编造素材中不存在的信息`;
|
|
19
|
+
|
|
20
|
+
module.exports = function elicit(args) {
|
|
21
|
+
const dir = resolveDir(args[0]);
|
|
22
|
+
const filePath = path.join(dir, '02-elicitation.md');
|
|
23
|
+
|
|
24
|
+
console.log('📋 R1·采集 — 生成 02 需求采集');
|
|
25
|
+
console.log('');
|
|
26
|
+
|
|
27
|
+
// Read existing raw materials if any
|
|
28
|
+
const rawFiles = fs.readdirSync(process.cwd()).filter(f => f.endsWith('.md') && f !== '02-elicitation.md');
|
|
29
|
+
const rawContent = rawFiles.map(f => {
|
|
30
|
+
const content = fs.readFileSync(path.join(process.cwd(), f), 'utf-8');
|
|
31
|
+
return `### ${f}\n${content.substring(0, 3000)}`;
|
|
32
|
+
}).join('\n\n');
|
|
33
|
+
|
|
34
|
+
console.log('📄 已读取项目中的素材文件作为输入。');
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log('请将以下 Prompt 复制到 AI 编码工具中执行:');
|
|
37
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
38
|
+
console.log(PROMPT);
|
|
39
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
40
|
+
console.log('');
|
|
41
|
+
if (rawContent) {
|
|
42
|
+
console.log('📎 上下文素材(已自动附加):');
|
|
43
|
+
console.log(rawContent.substring(0, 500));
|
|
44
|
+
if (rawContent.length > 500) console.log('...(已截断)');
|
|
45
|
+
}
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log('💡 提示:将 Prompt + 上下文素材一起发给 AI,让它生成 02-elicitation.md');
|
|
48
|
+
console.log(' 生成后保存到: ' + filePath);
|
|
49
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// mumuspec freeze — 版本冻结
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
const { resolveDir } = require('../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = function(args) {
|
|
6
|
+
const dir = resolveDir(args[0]);
|
|
7
|
+
const tag = args[1] || 'spec-v' + new Date().toISOString().slice(0,10).replace(/-/g,'');
|
|
8
|
+
|
|
9
|
+
console.log('🔒 版本冻结 — 锁定当前 Spec');
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log(' Spec 目录: ' + dir);
|
|
12
|
+
console.log(' Tag 名称: ' + tag);
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log('请执行以下操作完成冻结:');
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log(' 1. 确认所有 Spec 文档已通过校验: mumuspec validate');
|
|
17
|
+
console.log(' 2. 提交当前版本:');
|
|
18
|
+
console.log(' git add ' + dir + '/');
|
|
19
|
+
console.log(' git commit -m "freeze: ' + tag + '"');
|
|
20
|
+
console.log(' git tag ' + tag);
|
|
21
|
+
console.log(' 3. 推送:');
|
|
22
|
+
console.log(' git push origin main --tags');
|
|
23
|
+
console.log('');
|
|
24
|
+
console.log(' 冻结后:');
|
|
25
|
+
console.log(' - 代码生成必须以冻结版 Spec 为准');
|
|
26
|
+
console.log(' - 变更需求必须先解锁 → 更新 Spec → 重新冻结');
|
|
27
|
+
console.log(' - PR 必须关联 Spec 编号');
|
|
28
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// mumuspec impact — 变更影响分析
|
|
2
|
+
const { resolveDir, readSpec, findSpec } = require('../utils');
|
|
3
|
+
|
|
4
|
+
const DEPENDENCIES = {
|
|
5
|
+
'01': [], '02': ['01'], '03': ['02'], '04': ['03'],
|
|
6
|
+
'05': ['04'], '06': ['05','04'], '07': ['04'],
|
|
7
|
+
'08': ['07'], '09': ['05','06'], '10': ['08','09'],
|
|
8
|
+
'11': ['07','08'], '12': ['08','09','10'],
|
|
9
|
+
'13': ['05','06','09','10','11','12'],
|
|
10
|
+
'14': ['01','02','03','04','05','06','07','08','09','10','11','12','13']
|
|
11
|
+
};
|
|
12
|
+
const REVERSE = {};
|
|
13
|
+
Object.entries(DEPENDENCIES).forEach(([k, deps]) => deps.forEach(d => { if (!REVERSE[d]) REVERSE[d] = []; REVERSE[d].push(k); }));
|
|
14
|
+
|
|
15
|
+
module.exports = function(args) {
|
|
16
|
+
const dir = resolveDir(args[1]);
|
|
17
|
+
const specId = args[0];
|
|
18
|
+
if (!specId) { console.log('用法: mumuspec impact <spec-id> [dir]\n示例: mumuspec impact 05'); process.exit(1); }
|
|
19
|
+
|
|
20
|
+
const pureId = specId.replace(/^0+/,'');
|
|
21
|
+
const downstream = REVERSE[pureId] || [];
|
|
22
|
+
|
|
23
|
+
console.log('💥 变更影响分析 — Spec ' + (String(pureId).padStart(2,'0')));
|
|
24
|
+
console.log('');
|
|
25
|
+
if (downstream.length === 0) {
|
|
26
|
+
console.log(' ✅ 无下游依赖文档。此文档变更不影响其他 Spec。');
|
|
27
|
+
} else {
|
|
28
|
+
console.log(' 📋 受影响的文档(需同步更新):');
|
|
29
|
+
const affected = [];
|
|
30
|
+
function collect(id, depth) { if (!affected.includes(id)) { affected.push(id); (REVERSE[id]||[]).forEach(d => collect(d, depth+1)); } }
|
|
31
|
+
downstream.forEach(d => collect(d, 1));
|
|
32
|
+
|
|
33
|
+
affected.forEach(id => {
|
|
34
|
+
const f = findSpec(dir, id);
|
|
35
|
+
const status = f ? '存在' : '缺失';
|
|
36
|
+
console.log(' ' + String(id).padStart(2,'0') + ' — ' + status + (f ? '' : ' ⚠️'));
|
|
37
|
+
});
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log(` 总计 ${affected.length} 份文档受影响`);
|
|
40
|
+
}
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log('💡 运行 mumuspec sync ' + specId + ' 自动更新关联文档');
|
|
43
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// R2·提案 — 03 立项提案 + 04 PRD
|
|
2
|
+
const { resolveDir, readSpec, findSpec } = require('../utils');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
module.exports = function(args) {
|
|
6
|
+
const dir = resolveDir(args[0]);
|
|
7
|
+
const s02 = readSpec(dir, 2);
|
|
8
|
+
|
|
9
|
+
console.log('📋 R2·提案 — 生成 03 立项提案 + 04 PRD');
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log('输入: 02 需求采集');
|
|
12
|
+
console.log('输出: ' + path.join(dir, '03-proposal.md'));
|
|
13
|
+
console.log(' ' + path.join(dir, '04-prd.md'));
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log('请将以下 Prompt 复制到 AI 编码工具中执行:');
|
|
16
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
17
|
+
console.log(`你是产品需求专家。请根据以下 02-需求采集文档,生成两份文档:
|
|
18
|
+
|
|
19
|
+
## 第一步:生成 03-立项提案
|
|
20
|
+
包含章节:执行摘要、问题陈述、方案概述、范围界定(含/不含)、成功标准(可度量指标)、时间线与里程碑、风险评估、干系人签批
|
|
21
|
+
|
|
22
|
+
## 第二步:生成 04-产品需求 PRD
|
|
23
|
+
包含章节:产品概述、用户画像(2-3个角色)、功能清单(ID/描述/优先级)、用户旅程(主流程+边缘场景)、业务规则、数据需求、集成点、验收标准
|
|
24
|
+
|
|
25
|
+
格式要求:
|
|
26
|
+
- 中文撰写,Markdown 表格
|
|
27
|
+
- 成功标准必须是可度量的(数字或明确的是/否判断)
|
|
28
|
+
- 用户故事用"作为[角色],我想要[功能],以便[价值]"格式
|
|
29
|
+
- 不要编造素材中不存在的信息,不确定的标注 [待确认]
|
|
30
|
+
|
|
31
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
32
|
+
if (s02) {
|
|
33
|
+
console.log('📎 02-需求采集(已自动附加):');
|
|
34
|
+
console.log(s02.substring(0, 800));
|
|
35
|
+
if (s02.length > 800) console.log('...(已截断,完整内容见文件)');
|
|
36
|
+
} else {
|
|
37
|
+
console.log('⚠️ 02-需求采集 内容为空或不存在。建议先运行 mumuspec elicit');
|
|
38
|
+
}
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log('💡 生成后保存为 03-proposal.md 和 04-prd.md');
|
|
41
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// mumuspec status — 项目状态
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { resolveDir, readSpec } = require('../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = function(args) {
|
|
6
|
+
const dir = resolveDir(args[0]);
|
|
7
|
+
const phases = [
|
|
8
|
+
{ name:'Meta', docs:[1,2] },
|
|
9
|
+
{ name:'Proposal', docs:[3,4] },
|
|
10
|
+
{ name:'Spec', docs:[5,6,7] },
|
|
11
|
+
{ name:'Design', docs:[8,9,10,11] },
|
|
12
|
+
{ name:'Plan & Test', docs:[12,13] },
|
|
13
|
+
{ name:'Trace', docs:[14] }
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
console.log('📊 MumuSpec 项目状态');
|
|
17
|
+
console.log(' Spec 目录: ' + dir);
|
|
18
|
+
console.log('');
|
|
19
|
+
|
|
20
|
+
let total = 0, filled = 0;
|
|
21
|
+
phases.forEach(p => {
|
|
22
|
+
const d = p.docs.map(n => { const c = readSpec(dir, n); const ok = c && c.replace(/#.*\n?/g,'').replace(/>.*\n?/g,'').trim().length > 50; total++; if(ok) filled++; return {n,ok}; });
|
|
23
|
+
const pFilled = d.filter(x=>x.ok).length;
|
|
24
|
+
const bar = '█'.repeat(pFilled) + '░'.repeat(d.length - pFilled);
|
|
25
|
+
console.log(' ' + p.name.padEnd(12) + ' ' + bar + ' ' + pFilled + '/' + d.length);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log(' 总计: ' + filled + '/' + total + ' 份已填写');
|
|
30
|
+
console.log(' 进度: ' + Math.round(filled/total*100) + '%');
|
|
31
|
+
if (filled === 14) console.log(' ✅ 全部就绪,可以冻结了: mumuspec freeze');
|
|
32
|
+
else if (filled >= 8) console.log(' 📝 进度过半,继续加油');
|
|
33
|
+
else console.log(' 🚀 刚开始,可以先跑 mumuspec elicit → propose → anchor');
|
|
34
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// mumuspec sync — 变更联动
|
|
2
|
+
const { resolveDir } = require('../utils');
|
|
3
|
+
|
|
4
|
+
module.exports = function(args) {
|
|
5
|
+
const dir = resolveDir(args[1]);
|
|
6
|
+
const specId = args[0];
|
|
7
|
+
if (!specId) { console.log('用法: mumuspec sync <spec-id> [dir]\n示例: mumuspec sync 04'); process.exit(1); }
|
|
8
|
+
|
|
9
|
+
console.log('🔄 变更联动 — Spec ' + specId);
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log('此命令将分析 ' + specId + ' 的变更内容,并更新与之关联的下游文档。');
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log('💡 在 AI 编码工具中执行以下 Prompt:');
|
|
14
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
15
|
+
console.log(`我修改了 Spec-${specId}。请读取这份文档的最新内容,然后遍历所有依赖它的下游 Spec 文档,逐一检查是否需要更新。
|
|
16
|
+
|
|
17
|
+
更新原则:只修改受影响的字段和章节,保留其他内容不变。每个修改标注理由。
|
|
18
|
+
|
|
19
|
+
修改完成后运行 mumuspec validate 验证一致性。`);
|
|
20
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
21
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// mumuspec validate — 门禁校验
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { resolveDir, findSpec, readSpec } = require('../utils');
|
|
4
|
+
|
|
5
|
+
function checkAC(d05) { return (d05.match(/AC-\d+/gi) || []).length; }
|
|
6
|
+
function checkTrace(d05, d09) { const ac = d05.match(/AC-\d+/gi) || []; const api = d09.match(/US-\d+/gi) || []; return ac.length > 0 && api.length > 0; }
|
|
7
|
+
function checkSize(content) { const body = content.replace(/#.*\n?/g,'').replace(/>.*\n?/g,'').trim(); return body.length; }
|
|
8
|
+
|
|
9
|
+
module.exports = function(args) {
|
|
10
|
+
const dir = resolveDir(args[0]);
|
|
11
|
+
console.log('🔍 MumuSpec 门禁校验');
|
|
12
|
+
console.log('');
|
|
13
|
+
|
|
14
|
+
const checks = [
|
|
15
|
+
{ id: '01', label: '写作总则', fn: (c) => checkSize(c) > 80 },
|
|
16
|
+
{ id: '02', label: '需求采集', fn: (c) => checkSize(c) > 50 },
|
|
17
|
+
{ id: '03', label: '立项提案', fn: (c) => checkSize(c) > 100 },
|
|
18
|
+
{ id: '04', label: '产品需求 PRD', fn: (c) => checkSize(c) > 100 },
|
|
19
|
+
{ id: '05', label: '用户故事', fn: (c) => checkSize(c) > 80 && checkAC(c) >= 3 },
|
|
20
|
+
{ id: '06', label: '功能规格 FSD', fn: (c) => checkSize(c) > 80 },
|
|
21
|
+
{ id: '07', label: '非功能需求', fn: (c) => checkSize(c) > 50 },
|
|
22
|
+
{ id: '08', label: '架构选型', fn: (c) => checkSize(c) > 50 },
|
|
23
|
+
{ id: '09', label: 'API 契约', fn: (c) => checkSize(c) > 80 },
|
|
24
|
+
{ id: '10', label: '数据模型', fn: (c) => checkSize(c) > 50 },
|
|
25
|
+
{ id: '11', label: '安全设计', fn: (c) => checkSize(c) > 50 },
|
|
26
|
+
{ id: '12', label: '实施计划', fn: (c) => checkSize(c) > 50 },
|
|
27
|
+
{ id: '13', label: '测试策略', fn: (c) => checkSize(c) > 50 },
|
|
28
|
+
{ id: '14', label: '追溯矩阵', fn: (c) => checkSize(c) > 50 },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
let pass = 0, fail = 0;
|
|
32
|
+
checks.forEach(ch => {
|
|
33
|
+
const c = readSpec(dir, parseInt(ch.id));
|
|
34
|
+
const ok = c && ch.fn(c);
|
|
35
|
+
console.log((ok ? ' ✅' : ' ❌') + ' ' + ch.id.padStart(2,' ') + ' ' + ch.label + (!c ? ' (文件缺失)' : ok ? '' : ' (内容不足)'));
|
|
36
|
+
ok ? pass++ : fail++;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Cross-doc checks
|
|
40
|
+
const s05 = readSpec(dir, 5) || '';
|
|
41
|
+
const s09 = readSpec(dir, 9) || '';
|
|
42
|
+
const s14 = readSpec(dir, 14) || '';
|
|
43
|
+
const acCount = checkAC(s05);
|
|
44
|
+
let crossOk = true;
|
|
45
|
+
if (acCount > 0 && s09 && !checkTrace(s05, s09)) { console.log(' ⚠️ AC-API 追溯: 05 有 AC 但 09 未关联 US 编号'); crossOk = false; }
|
|
46
|
+
if (s14.length < 50 && acCount > 0) { console.log(' ⚠️ RTM 追溯: 05 有 AC 但 14 追溯矩阵为空'); crossOk = false; }
|
|
47
|
+
|
|
48
|
+
console.log('');
|
|
49
|
+
if (pass === 14 && crossOk) { console.log('✅ 全部 14 份文档通过门禁校验'); }
|
|
50
|
+
else { console.log(`📊 通过 ${pass}/14,未通过 ${fail}/14`); }
|
|
51
|
+
process.exit(pass === 14 ? 0 : 1);
|
|
52
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// mumuspec view — 角色视图
|
|
2
|
+
const { resolveDir, readSpec } = require('../utils');
|
|
3
|
+
|
|
4
|
+
const ROLE_DOCS = {
|
|
5
|
+
pm: { label:'产品经理', docs:[2,3,4,5,6], desc:'需求采集→立项→PRD→用户故事→FSD' },
|
|
6
|
+
dev: { label:'开发者', docs:[5,6,9,10,12], desc:'用户故事→FSD→API→数据→实施计划' },
|
|
7
|
+
qa: { label:'QA', docs:[5,6,9,13,14], desc:'用户故事→FSD→API→测试策略→RTM' },
|
|
8
|
+
arch: { label:'架构师', docs:[1,7,8,11], desc:'写作总则→NFR→架构→安全' },
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
module.exports = function(args) {
|
|
12
|
+
const roleArg = args.indexOf('--role') > -1 ? args[args.indexOf('--role')+1] : null;
|
|
13
|
+
let dir = process.cwd();
|
|
14
|
+
for (const a of args) { if (!a.startsWith('--') && a !== roleArg) { dir = require('../utils').resolveDir(a); break; } }
|
|
15
|
+
|
|
16
|
+
if (!roleArg || !ROLE_DOCS[roleArg]) {
|
|
17
|
+
console.log('用法: mumuspec view --role <pm|dev|qa|arch> [dir]');
|
|
18
|
+
console.log(' pm — 产品经理视角 (02-06)');
|
|
19
|
+
console.log(' dev — 开发者视角 (05-06-09-10-12)');
|
|
20
|
+
console.log(' qa — QA 视角 (05-06-09-13-14)');
|
|
21
|
+
console.log(' arch — 架构师视角 (01-07-08-11)');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const role = ROLE_DOCS[roleArg];
|
|
26
|
+
console.log('👤 ' + role.label + '视角');
|
|
27
|
+
console.log(' ' + role.desc);
|
|
28
|
+
console.log('');
|
|
29
|
+
|
|
30
|
+
role.docs.forEach(n => {
|
|
31
|
+
const c = readSpec(dir, n);
|
|
32
|
+
const status = c ? (c.replace(/#.*\n?/g,'').replace(/>.*\n?/g,'').trim().length > 50 ? '✅' : '⚠️') : '❌';
|
|
33
|
+
const file = (require('../utils').findSpec(dir, n) || '').replace(dir+'/','');
|
|
34
|
+
console.log(' ' + status + ' ' + String(n).padStart(2,'0') + ' ' + (file || '(缺失)'));
|
|
35
|
+
});
|
|
36
|
+
console.log('');
|
|
37
|
+
console.log('💡 用 AI 工具打开上述文件,让 AI 基于这些文档开始工作');
|
|
38
|
+
};
|
package/src/templates.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
'01-writing-guidelines.md': '# 01 — 写作总则\n\n> 文档编号:SPEC-01 | 阶段:Meta | 优先级:P1\n\n## 1. 目的\n定义本套 Spec 的书写规范、术语和格式约定。\n\n## 2. 受众\n| 角色 | 如何使用本套 Spec |\n|------|------------------|\n| 产品经理 | 编写 02-06 号文档 |\n| 架构师 | 编写 07-08、11 号文档 |\n| 开发者 | 依据 06、09、10 号文档进行开发 |\n| QA | 依据 05、13、14 号文档设计测试 |\n\n## 3. 术语表\n| 术语 | 定义 |\n|------|------|\n| | |\n\n## 4. 格式规范\n- 文件名: `XX-document-name.md`\n- 文档版本: 在文档元数据区标注\n- 编号规则: 01-14 固定编号,不可重复\n\n## 5. 版本控制\n- 每份文档有独立的版本号\n- 冻结后打 Git tag\n- 变更记录写在文档末尾\n\n## 6. AI 辅助规范\n- AI 编码工具应读取本套 Spec 作为上下文\n- Spec 冻结后禁止 AI 修改 Spec 文档\n',
|
|
3
|
+
'02-elicitation.md': '# 02 — 需求采集\n\n> 文档编号:SPEC-02 | 阶段:Meta | 优先级:P2\n\n## 1. 需求来源\n| 来源 | 描述 | 优先级 |\n|------|------|--------|\n| | | |\n\n## 2. 干系人清单\n| 姓名 | 角色 | 核心诉求 | 决策权限 |\n|------|------|---------|---------|\n| | | | |\n\n## 3. 业务背景\n_为什么这个需求重要?不做的后果是什么?_\n\n## 4. 初步假设\n_当前对需求的假设(需后续验证)_\n',
|
|
4
|
+
'03-proposal.md': '# 03 — 立项提案\n\n> 文档编号:SPEC-03 | 阶段:Proposal | 优先级:P0\n\n## 1. 执行摘要\n_一段话说明项目内容和价值_\n\n## 2. 问题陈述\n_当前的问题是什么,影响多大?_\n\n## 3. 方案概述\n_高层级的解决思路_\n\n## 4. 范围界定\n### 4.1 包含范围\n### 4.2 不包含范围\n\n## 5. 成功标准\n| 指标 | 目标值 | 度量方式 |\n|------|--------|---------|\n| | | |\n\n## 6. 时间线与里程碑\n| 里程碑 | 目标日期 | 交付物 |\n|--------|---------|--------|\n| | | |\n\n## 7. 风险评估\n| 风险 | 可能性 | 影响 | 缓解措施 |\n|------|--------|------|---------|\n| | | | |\n\n## 8. 干系人签批\n| 姓名 | 角色 | 审批意见 | 日期 |\n|------|------|---------|------|\n| | | | |\n',
|
|
5
|
+
'04-prd.md': '# 04 — 产品需求文档 (PRD)\n\n> 文档编号:SPEC-04 | 阶段:Proposal | 优先级:P1\n\n## 1. 产品概述\n_产品/功能是什么?为谁服务?_\n\n## 2. 用户画像\n| 画像 | 描述 | 核心目标 |\n|------|------|---------|\n| | | |\n\n## 3. 功能清单\n| ID | 功能 | 描述 | 优先级 | 状态 |\n|----|------|------|--------|------|\n| | | | | |\n\n## 4. 用户旅程\n### 4.1 主流程\n### 4.2 边缘场景\n\n## 5. 业务规则\n| ID | 规则 | 触发条件 | 执行动作 |\n|----|------|---------|---------|\n| | | | |\n\n## 6. 数据需求\n## 7. 集成点\n## 8. 验收标准(高层)\n',
|
|
6
|
+
'05-user-stories.md': '# 05 — 用户故事与验收标准\n\n> 文档编号:SPEC-05 | 阶段:Spec | 优先级:P0\n\n## 1. 故事清单\n| ID | 作为[角色] | 我想要[功能] | 以便[价值] | 优先级 |\n|----|-----------|-------------|-----------|--------|\n| US-001 | | | | |\n\n## 2. 验收条件 (AC)\n### US-001\n| AC-ID | 验收条件 | 验证方式 |\n|-------|---------|---------|\n| AC-001 | | |\n\n## 3. 优先级排序(MoSCoW)\n## 4. 故事依赖关系\n',
|
|
7
|
+
'06-fsd.md': '# 06 — 功能规格说明 (FSD)\n\n> 文档编号:SPEC-06 | 阶段:Spec | 优先级:P0\n\n## 1. 功能概述\n## 2. 交互流程\n## 3. 界面说明\n## 4. 异常处理\n| 异常场景 | 处理方式 | 用户提示 |\n|---------|---------|---------|\n| | | |\n\n## 5. 状态机\n## 6. 边界条件\n',
|
|
8
|
+
'07-nfr.md': '# 07 — 非功能需求 (NFR)\n\n> 文档编号:SPEC-07 | 阶段:Spec | 优先级:P1\n\n## 1. 性能要求\n| 指标 | 目标 | 度量方式 |\n|------|------|---------|\n| | | |\n\n## 2. 可用性要求\n## 3. 安全性要求\n## 4. 兼容性要求\n## 5. 可维护性要求\n',
|
|
9
|
+
'08-architecture.md': '# 08 — 系统架构与技术选型\n\n> 文档编号:SPEC-08 | 阶段:Design | 优先级:P1\n\n## 1. 架构概述\n## 2. 技术选型\n## 3. 架构决策记录 (ADR)\n## 4. 部署架构\n## 5. 依赖关系\n## 6. 风险与权衡\n',
|
|
10
|
+
'09-api-spec.md': '# 09 — API 契约\n\n> 文档编号:SPEC-09 | 阶段:Design | 优先级:P0\n\n## 1. API 概览\n| 属性 | 值 |\n|------|-----|\n| Base URL | |\n| 版本 | v1 |\n| 认证方式 | |\n| 数据格式 | JSON |\n\n## 2. 端点列表\n\n## 3. 通用数据模型\n## 4. 错误响应格式\n```json\n{ "error": { "code": "string", "message": "string" } }\n```\n',
|
|
11
|
+
'10-data-model.md': '# 10 — 数据模型与存储规格\n\n> 文档编号:SPEC-10 | 阶段:Design | 优先级:P0\n\n## 1. 实体关系图 (ER)\n## 2. 表结构\n## 3. 索引策略\n## 4. 数据流\n## 5. 迁移方案\n',
|
|
12
|
+
'11-security.md': '# 11 — 安全设计\n\n> 文档编号:SPEC-11 | 阶段:Design | 优先级:P2\n\n## 1. 攻击面分析\n## 2. 权限模型\n## 3. 数据安全\n## 4. 安全控制措施\n## 5. 审计与日志\n## 6. 合规映射\n',
|
|
13
|
+
'12-implementation-plan.md': '# 12 — 实施计划与里程碑\n\n> 文档编号:SPEC-12 | 阶段:Plan | 优先级:P2\n\n## 1. 里程碑\n| 里程碑 | 目标日期 | 交付物 | 验收标准 |\n|--------|---------|--------|---------|\n| | | | |\n\n## 2. 任务分解 (WBS)\n## 3. 依赖关系\n## 4. 风险矩阵\n## 5. 回滚方案\n',
|
|
14
|
+
'13-test-strategy.md': '# 13 — 测试策略与质量门禁\n\n> 文档编号:SPEC-13 | 阶段:Test | 优先级:P0\n\n## 1. 测试范围\n## 2. 测试策略\n| 层级 | 范围 | 工具 | 覆盖率目标 |\n|------|------|------|-----------|\n| | | | |\n\n## 3. 测试环境\n## 4. 通过标准\n## 5. 自动化策略\n## 6. 追溯\n',
|
|
15
|
+
'14-traceability-matrix.md': '# 14 — 需求追溯矩阵 (RTM)\n\n> 文档编号:SPEC-14 | 阶段:Trace | 优先级:P2\n\n## 1. 完整追溯矩阵\n| 需求 ID | PRD 功能 | 用户故事 | FSD 章节 | API 端点 | 数据模型 | 测试用例 | 状态 |\n|---------|---------|---------|---------|---------|---------|---------|------|\n| | | | | | | | |\n\n## 2. 覆盖率汇总\n## 3. 缺口与风险\n## 4. 变更日志\n',
|
|
16
|
+
};
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function resolveDir(arg) {
|
|
5
|
+
if (arg) return path.resolve(arg);
|
|
6
|
+
// Check common Spec directories
|
|
7
|
+
for (const d of ['specs', 'spec', 'Specs', 'Spec']) {
|
|
8
|
+
if (fs.existsSync(d)) return path.resolve(d);
|
|
9
|
+
}
|
|
10
|
+
return process.cwd();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function findSpec(dir, num) {
|
|
14
|
+
const prefix = String(num).padStart(2,'0');
|
|
15
|
+
const files = fs.readdirSync(dir).filter(f => f.startsWith(prefix) && f.endsWith('.md'));
|
|
16
|
+
return files[0] ? path.join(dir, files[0]) : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readSpec(dir, num) {
|
|
20
|
+
const f = findSpec(dir, num);
|
|
21
|
+
return f ? fs.readFileSync(f, 'utf-8') : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function specExists(dir, num) {
|
|
25
|
+
const content = readSpec(dir, num);
|
|
26
|
+
return !!(content && content.replace(/#.*\n?/g,'').replace(/>.*\n?/g,'').trim().length > 50);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { resolveDir, findSpec, readSpec, specExists };
|