prd-workflow-cli 1.1.25
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/.agent/workflows/prd-b1-planning-draft.md +501 -0
- package/.agent/workflows/prd-b2-planning-breakdown.md +731 -0
- package/.agent/workflows/prd-c1-requirement-list.md +907 -0
- package/.agent/workflows/prd-c2-change-tracking.md +320 -0
- package/.agent/workflows/prd-dialog-archive.md +215 -0
- package/.agent/workflows/prd-p0-project-info.md +379 -0
- package/.agent/workflows/prd-r0-baseline-review.md +925 -0
- package/.agent/workflows/prd-r1-review.md +458 -0
- package/.agent/workflows/prd-r2-review.md +483 -0
- package/.antigravity/rules.md +238 -0
- package/.cursorrules +284 -0
- package/GUIDE.md +341 -0
- package/README.md +416 -0
- package/bin/prd-cli.js +134 -0
- package/commands/baseline.js +470 -0
- package/commands/change.js +151 -0
- package/commands/confirm.js +364 -0
- package/commands/dialog.js +227 -0
- package/commands/index.js +365 -0
- package/commands/init.js +357 -0
- package/commands/iteration.js +192 -0
- package/commands/planning.js +710 -0
- package/commands/review.js +444 -0
- package/commands/status.js +142 -0
- package/commands/upgrade.js +228 -0
- package/commands/version.js +794 -0
- package/package.json +74 -0
- package/scripts/postinstall.js +241 -0
- package/templates/README-FOR-NEW-USER.md +105 -0
- package/templates/dialog-template.md +109 -0
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const confirm = require('./confirm');
|
|
5
|
+
const dialog = require('./dialog');
|
|
6
|
+
|
|
7
|
+
module.exports = async function (action, type, options = {}) {
|
|
8
|
+
const configPath = path.join(process.cwd(), '.prd-config.json');
|
|
9
|
+
|
|
10
|
+
if (!await fs.pathExists(configPath)) {
|
|
11
|
+
console.log(chalk.red('✗ 当前目录不是一个 PRD 项目'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const config = await fs.readJSON(configPath);
|
|
16
|
+
|
|
17
|
+
if (action === 'create') {
|
|
18
|
+
await createPlanDoc(type, config, configPath, options);
|
|
19
|
+
} else if (action === 'freeze') {
|
|
20
|
+
await freezePlan(config, configPath, options);
|
|
21
|
+
} else {
|
|
22
|
+
console.log(chalk.red('✗ 未知操作'));
|
|
23
|
+
console.log('可用操作: create B1|B2, freeze');
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
async function createPlanDoc(type, config, configPath, options = {}) {
|
|
28
|
+
if (config.currentIteration === 0) {
|
|
29
|
+
console.log(chalk.red('✗ 请先创建迭代'));
|
|
30
|
+
console.log('运行: prd iteration new');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const iterationDir = path.join(
|
|
35
|
+
process.cwd(),
|
|
36
|
+
'02_迭代记录',
|
|
37
|
+
`第${String(config.currentIteration).padStart(2, '0')}轮迭代`
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const templates = {
|
|
41
|
+
'B1': getB1Template(),
|
|
42
|
+
'B2': getB2Template()
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (!templates[type]) {
|
|
46
|
+
console.log(chalk.red(`✗ 未知的文档类型: ${type}`));
|
|
47
|
+
console.log('可用类型: B1, B2');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const fileName = getFileName(type);
|
|
52
|
+
const filePath = path.join(iterationDir, fileName);
|
|
53
|
+
|
|
54
|
+
if (await fs.pathExists(filePath)) {
|
|
55
|
+
console.log(chalk.yellow(`⚠ 文件已存在: ${fileName}`));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// B1 需要强制 PM 确认 R1 启动条件
|
|
60
|
+
if (type === 'B1') {
|
|
61
|
+
// ⭐ 首先检查 A 类基线文档是否完整
|
|
62
|
+
const baselineDir = path.join(process.cwd(), '01_产品基线');
|
|
63
|
+
const a0Path = path.join(baselineDir, 'A0_产品基础与范围说明.md');
|
|
64
|
+
const a1Path = path.join(baselineDir, 'A1_已上线功能与流程清单.md'); // 修正文件名
|
|
65
|
+
const a2Path = path.join(baselineDir, 'A2_存量反馈与数据汇总.md'); // 修正文件名
|
|
66
|
+
|
|
67
|
+
const missingDocs = [];
|
|
68
|
+
if (!await fs.pathExists(a0Path)) missingDocs.push('A0_产品基础与范围说明');
|
|
69
|
+
if (!await fs.pathExists(a1Path)) missingDocs.push('A1_已上线功能与流程清单'); // 修正显示名
|
|
70
|
+
if (!await fs.pathExists(a2Path)) missingDocs.push('A2_存量反馈与数据汇总'); // 修正显示名
|
|
71
|
+
|
|
72
|
+
if (missingDocs.length > 0) {
|
|
73
|
+
console.log(chalk.red('\n✗ A 类基线文档不完整,无法开始规划\n'));
|
|
74
|
+
console.log(chalk.yellow('缺失的文档:'));
|
|
75
|
+
missingDocs.forEach(doc => console.log(` - ${doc}`));
|
|
76
|
+
console.log('');
|
|
77
|
+
console.log(chalk.bold('请先完成基线文档:'));
|
|
78
|
+
if (missingDocs.includes('A0_产品基础与范围说明')) {
|
|
79
|
+
console.log(' prd baseline create A0');
|
|
80
|
+
}
|
|
81
|
+
if (missingDocs.includes('A1_已上线功能清单')) {
|
|
82
|
+
console.log(' prd baseline create A1');
|
|
83
|
+
}
|
|
84
|
+
if (missingDocs.includes('A2_存量反馈汇总')) {
|
|
85
|
+
console.log(' prd baseline create A2');
|
|
86
|
+
}
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log(chalk.gray('提示: 如果用户已提供功能清单或反馈信息,应先归档到对应的 A 类文档'));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(chalk.green('✓ A 类基线文档完整'));
|
|
93
|
+
|
|
94
|
+
const r1StartPath = path.join(iterationDir, 'R1_规划启动条件检查.md');
|
|
95
|
+
if (!await fs.pathExists(r1StartPath)) {
|
|
96
|
+
console.log(chalk.red('✗ 请先完成 R1 规划启动条件检查'));
|
|
97
|
+
console.log('运行: prd iteration new');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ⭐ 支持预确认模式:PM 已在对话中确认
|
|
102
|
+
let r1Confirmed = false;
|
|
103
|
+
if (options.pmConfirmed) {
|
|
104
|
+
console.log(chalk.green('✓ PM 已在对话中确认 R1 三个启动条件满足'));
|
|
105
|
+
r1Confirmed = true;
|
|
106
|
+
await dialog.logPMConfirmation('planning', 'start_b1', 'approved', 'PM通过对话确认R1三条件满足,启动规划(预确认模式)');
|
|
107
|
+
} else if (process.env.PRD_TEST_MODE === 'true') {
|
|
108
|
+
// 测试模式:自动确认
|
|
109
|
+
console.log(chalk.yellow('⚠️ 测试模式:自动确认 R1 启动条件'));
|
|
110
|
+
r1Confirmed = true;
|
|
111
|
+
} else {
|
|
112
|
+
// 交互式确认
|
|
113
|
+
r1Confirmed = await confirm.confirmR1Start();
|
|
114
|
+
if (r1Confirmed) {
|
|
115
|
+
await dialog.logPMConfirmation('planning', 'start_b1', 'approved', 'PM确认R1三条件满足,启动规划');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!r1Confirmed) {
|
|
120
|
+
console.log(chalk.yellow('\n根据 PM 决策,未启动规划'));
|
|
121
|
+
console.log(chalk.gray('提示:只有满足三个启动条件,才应开始规划\n'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(chalk.green('\n✓ PM 确认启动规划\n'));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// B2 需要检查 B1 是否存在
|
|
129
|
+
if (type === 'B2') {
|
|
130
|
+
const b1Path = path.join(iterationDir, 'B1_需求规划草案.md');
|
|
131
|
+
if (!await fs.pathExists(b1Path)) {
|
|
132
|
+
console.log(chalk.red('✗ 请先创建 B1'));
|
|
133
|
+
console.log('运行: prd plan create B1');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 写入文件
|
|
139
|
+
await fs.writeFile(filePath, templates[type]);
|
|
140
|
+
|
|
141
|
+
// 记录文档创建
|
|
142
|
+
await dialog.logDocumentCreation('planning', type, filePath);
|
|
143
|
+
|
|
144
|
+
console.log(chalk.green(`✓ ${fileName} 创建成功!`));
|
|
145
|
+
console.log(chalk.cyan(`文件位置: ${filePath}\n`));
|
|
146
|
+
|
|
147
|
+
if (type === 'B1') {
|
|
148
|
+
console.log(chalk.bold('⚠️ 重要提醒:\n'));
|
|
149
|
+
console.log(chalk.yellow('【PM 职责】'));
|
|
150
|
+
console.log('- 提出真实规划意图');
|
|
151
|
+
console.log('- 确认目标与场景');
|
|
152
|
+
console.log('- 明确"不做什么"\n');
|
|
153
|
+
|
|
154
|
+
console.log(chalk.cyan('【AI 职责】'));
|
|
155
|
+
console.log('- 组织规划结构');
|
|
156
|
+
console.log('- 发现目标冲突');
|
|
157
|
+
console.log('- 检查是否偏离现状\n');
|
|
158
|
+
|
|
159
|
+
console.log(chalk.red('【AI 禁止】'));
|
|
160
|
+
console.log('- ❌ 擅自扩展规划范围\n');
|
|
161
|
+
|
|
162
|
+
console.log(chalk.bold('下一步:'));
|
|
163
|
+
console.log('1. PM 填写 B1_需求规划草案.md (AI 可辅助但需 PM 确认)');
|
|
164
|
+
console.log('2. 创建 B2: prd plan create B2');
|
|
165
|
+
} else if (type === 'B2') {
|
|
166
|
+
console.log(chalk.bold('⚠️ 重要提醒:\n'));
|
|
167
|
+
console.log(chalk.yellow('【PM 职责】'));
|
|
168
|
+
console.log('- 决定取舍');
|
|
169
|
+
console.log('- 决定优先级');
|
|
170
|
+
console.log('- 接受或拒绝拆解建议\n');
|
|
171
|
+
|
|
172
|
+
console.log(chalk.cyan('【AI 职责】'));
|
|
173
|
+
console.log('- 提出多种拆解方式');
|
|
174
|
+
console.log('- 暴露范围风险');
|
|
175
|
+
console.log('- 标注依赖关系\n');
|
|
176
|
+
|
|
177
|
+
console.log(chalk.red('【AI 禁止】'));
|
|
178
|
+
console.log('- ❌ 替 PM 做取舍决策\n');
|
|
179
|
+
|
|
180
|
+
console.log(chalk.bold('下一步:'));
|
|
181
|
+
console.log('1. PM 填写 B2_规划拆解与范围界定.md');
|
|
182
|
+
console.log('2. 执行 R1 审视: prd review r1');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function freezePlan(config, configPath, options = {}) {
|
|
187
|
+
if (config.currentIteration === 0) {
|
|
188
|
+
console.log(chalk.red('✗ 请先创建迭代'));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const iterationDir = path.join(
|
|
193
|
+
process.cwd(),
|
|
194
|
+
'02_迭代记录',
|
|
195
|
+
`第${String(config.currentIteration).padStart(2, '0')}轮迭代`
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// 检查 B1, B2 是否存在
|
|
199
|
+
const b1Path = path.join(iterationDir, 'B1_需求规划草案.md');
|
|
200
|
+
const b2Path = path.join(iterationDir, 'B2_规划拆解与范围界定.md');
|
|
201
|
+
|
|
202
|
+
if (!await fs.pathExists(b1Path) || !await fs.pathExists(b2Path)) {
|
|
203
|
+
console.log(chalk.red('✗ 请先完成 B1 和 B2'));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 检查 R1 审视是否通过
|
|
208
|
+
const r1ReviewPath = path.join(iterationDir, 'R1_规划审视报告.md');
|
|
209
|
+
if (!await fs.pathExists(r1ReviewPath)) {
|
|
210
|
+
console.log(chalk.red('✗ 请先完成 R1 规划审视'));
|
|
211
|
+
console.log('运行: prd review r1');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 读取 R1 审视结论
|
|
216
|
+
const r1Content = await fs.readFile(r1ReviewPath, 'utf-8');
|
|
217
|
+
const hasPassed = r1Content.includes('- [x] ✅ 通过') || r1Content.includes('[x] 通过');
|
|
218
|
+
|
|
219
|
+
if (!hasPassed) {
|
|
220
|
+
console.log(chalk.red('✗ R1 审视未通过,不能冻结规划'));
|
|
221
|
+
console.log(chalk.yellow('请修改 B1/B2 后重新执行 R1 审视'));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ⭐ 支持预确认模式:PM 已在对话中确认并提供签名
|
|
226
|
+
let pmSignature = null;
|
|
227
|
+
if (options.pmConfirmed && options.pmSignature) {
|
|
228
|
+
console.log(chalk.green(`✓ PM 已在对话中确认冻结,签名: ${options.pmSignature}`));
|
|
229
|
+
pmSignature = options.pmSignature;
|
|
230
|
+
} else {
|
|
231
|
+
// 交互式确认
|
|
232
|
+
pmSignature = await confirm.confirmB3Freeze();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!pmSignature) {
|
|
236
|
+
console.log(chalk.yellow('\n根据 PM 决策,未执行冻结'));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ⭐ 读取 B1、B2、R1 内容,提取关键信息
|
|
241
|
+
console.log(chalk.gray('正在从 B1/B2/R1 提取关键信息...'));
|
|
242
|
+
|
|
243
|
+
const b1Content = await fs.readFile(b1Path, 'utf-8');
|
|
244
|
+
const b2Content = await fs.readFile(b2Path, 'utf-8');
|
|
245
|
+
|
|
246
|
+
// 提取 B1 核心目标(尝试从多个可能的标题下提取)
|
|
247
|
+
let b1CoreGoal = extractSection(b1Content, '要解决的核心问题') ||
|
|
248
|
+
extractSection(b1Content, '核心问题') ||
|
|
249
|
+
extractSection(b1Content, '规划目标') ||
|
|
250
|
+
'(请手动填写,未能自动提取)';
|
|
251
|
+
|
|
252
|
+
// 提取 B2 范围说明
|
|
253
|
+
let b2Scope = extractSection(b2Content, '首版包含') ||
|
|
254
|
+
extractSection(b2Content, '范围界定') ||
|
|
255
|
+
extractSection(b2Content, '包含范围') ||
|
|
256
|
+
'(请手动填写,未能自动提取)';
|
|
257
|
+
|
|
258
|
+
// 提取 R1 审视详情
|
|
259
|
+
let r1Summary = '';
|
|
260
|
+
const r1Sections = ['目标清晰性', '场景真实性', '现状一致性', '范围收敛性', '版本化准备度'];
|
|
261
|
+
for (const section of r1Sections) {
|
|
262
|
+
const sectionContent = extractSection(r1Content, section);
|
|
263
|
+
if (sectionContent && sectionContent.length > 10) {
|
|
264
|
+
r1Summary += `- ${section}: ${sectionContent.substring(0, 100)}...\n`;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (!r1Summary) {
|
|
268
|
+
r1Summary = '(请参考 R1_规划审视报告.md)';
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 检查 R1 中的结论
|
|
272
|
+
let r1Conclusion = '✅ 通过';
|
|
273
|
+
if (r1Content.includes('有条件通过')) {
|
|
274
|
+
r1Conclusion = '⚠️ 有条件通过';
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 生成 B3(传入提取的内容)
|
|
278
|
+
const b3Template = getB3Template(pmSignature, {
|
|
279
|
+
b1CoreGoal,
|
|
280
|
+
b2Scope,
|
|
281
|
+
r1Summary,
|
|
282
|
+
r1Conclusion
|
|
283
|
+
});
|
|
284
|
+
const b3Path = path.join(iterationDir, 'B3_规划冻结归档.md');
|
|
285
|
+
await fs.writeFile(b3Path, b3Template);
|
|
286
|
+
|
|
287
|
+
// 记录 PM 决策和文档创建
|
|
288
|
+
await dialog.logPMConfirmation('planning', 'freeze_b3', 'approved',
|
|
289
|
+
`PM签名: ${pmSignature}, 规划冻结`
|
|
290
|
+
);
|
|
291
|
+
await dialog.logDocumentCreation('planning', 'B3', b3Path);
|
|
292
|
+
|
|
293
|
+
console.log(chalk.green('\n✓ B3_规划冻结归档.md 创建成功!'));
|
|
294
|
+
console.log(chalk.cyan(`文件位置: ${b3Path}\n`));
|
|
295
|
+
|
|
296
|
+
console.log(chalk.bold.green('🎉 规划已冻结!\n'));
|
|
297
|
+
console.log(chalk.bold('下一步:'));
|
|
298
|
+
console.log('1. 创建版本范围: prd version create C0');
|
|
299
|
+
console.log('2. 创建版本需求: prd version create C1');
|
|
300
|
+
console.log('3. 执行 R2 审视: prd review r2');
|
|
301
|
+
console.log('');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function getFileName(type) {
|
|
305
|
+
const names = {
|
|
306
|
+
'B1': 'B1_需求规划草案.md',
|
|
307
|
+
'B2': 'B2_规划拆解与范围界定.md'
|
|
308
|
+
};
|
|
309
|
+
return names[type];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function getB1Template() {
|
|
313
|
+
return `# B1_需求规划草案
|
|
314
|
+
|
|
315
|
+
**创建时间**: ${new Date().toLocaleString('zh-CN')}
|
|
316
|
+
**文档状态**: 草案
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 文档说明
|
|
321
|
+
|
|
322
|
+
**目的**:
|
|
323
|
+
- 描述"想解决什么问题"
|
|
324
|
+
- 明确规划目标和边界
|
|
325
|
+
- 说明为什么值得单独一轮规划
|
|
326
|
+
|
|
327
|
+
**填写要求**:
|
|
328
|
+
- 必须基于 A 类文档中的真实现状
|
|
329
|
+
- 必须说明"明确不做什么"
|
|
330
|
+
- 禁止引入 A 类中不存在的能力
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## 1. 规划目标
|
|
335
|
+
|
|
336
|
+
### 1.1 核心问题
|
|
337
|
+
|
|
338
|
+
**要解决的核心问题**:
|
|
339
|
+
<!-- 填写内容:描述具体要解决的问题,必须可在 A1/A2 中找到依据 -->
|
|
340
|
+
|
|
341
|
+
**问题来源**:
|
|
342
|
+
- [ ] A1: 现有功能/流程的明确断点 (具体章节: _______)
|
|
343
|
+
- [ ] A2: 真实用户反馈/数据异常 (具体反馈: _______)
|
|
344
|
+
- [ ] 业务约束变化/合规要求 (具体说明: _______)
|
|
345
|
+
|
|
346
|
+
**为什么值得单独规划**:
|
|
347
|
+
<!-- 说明为什么不能通过微调、修补解决 -->
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 2. 使用场景
|
|
352
|
+
|
|
353
|
+
### 2.1 目标用户
|
|
354
|
+
|
|
355
|
+
**核心用户群**:
|
|
356
|
+
<!-- 描述用户是谁,基于 A0 中定义的用户 -->
|
|
357
|
+
|
|
358
|
+
### 2.2 关键场景
|
|
359
|
+
|
|
360
|
+
**场景1**:
|
|
361
|
+
- 触发条件:
|
|
362
|
+
- 用户目标:
|
|
363
|
+
- 当前痛点: (引用 A1/A2 具体内容)
|
|
364
|
+
|
|
365
|
+
**场景2**:
|
|
366
|
+
<!-- 如有多个场景,继续列举 -->
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## 3. 规划范围
|
|
371
|
+
|
|
372
|
+
### 3.1 目标范围
|
|
373
|
+
|
|
374
|
+
**包含内容**:
|
|
375
|
+
1.
|
|
376
|
+
2.
|
|
377
|
+
3.
|
|
378
|
+
|
|
379
|
+
### 3.2 明确不做
|
|
380
|
+
|
|
381
|
+
**本轮规划不包含**:
|
|
382
|
+
1.
|
|
383
|
+
2.
|
|
384
|
+
3.
|
|
385
|
+
|
|
386
|
+
**理由**:
|
|
387
|
+
<!-- 说明为什么这些不在范围内 -->
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## 4. 核心需求(概述)
|
|
392
|
+
|
|
393
|
+
### 4.1 需求概要
|
|
394
|
+
|
|
395
|
+
**需求1**:
|
|
396
|
+
- 解决什么问题:
|
|
397
|
+
- 涉及哪些功能点:
|
|
398
|
+
|
|
399
|
+
**需求2**:
|
|
400
|
+
<!-- 继续列举核心需求 -->
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## 5. 约束与依赖
|
|
405
|
+
|
|
406
|
+
### 5.1 技术约束
|
|
407
|
+
|
|
408
|
+
**已知约束**:
|
|
409
|
+
- 现有架构限制: (参考 A0)
|
|
410
|
+
- 依赖现有能力: (参考 A1)
|
|
411
|
+
|
|
412
|
+
### 5.2 业务约束
|
|
413
|
+
|
|
414
|
+
**时间约束**:
|
|
415
|
+
**资源约束**:
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## 6. 成功标准
|
|
420
|
+
|
|
421
|
+
**如何判断规划成功**:
|
|
422
|
+
1.
|
|
423
|
+
2.
|
|
424
|
+
3.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 填写检查清单
|
|
429
|
+
|
|
430
|
+
- [ ] 所有问题都可在 A 类文档中找到依据
|
|
431
|
+
- [ ] 明确说明了"不做什么"
|
|
432
|
+
- [ ] 没有引入 A0 中不存在的能力
|
|
433
|
+
- [ ] 场景真实且可验证
|
|
434
|
+
- [ ] 范围收敛,可版本化
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
**填写人**: _____________
|
|
439
|
+
**填写日期**: _____________
|
|
440
|
+
`;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function getB2Template() {
|
|
444
|
+
return `# B2_规划拆解与范围界定
|
|
445
|
+
|
|
446
|
+
**创建时间**: ${new Date().toLocaleString('zh-CN')}
|
|
447
|
+
**文档状态**: 拆解中
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## 文档说明
|
|
452
|
+
|
|
453
|
+
**目的**:
|
|
454
|
+
- 将 B1 的规划目标拆解为可执行的需求项
|
|
455
|
+
- 确定优先级和范围
|
|
456
|
+
- 界定清晰的版本边界
|
|
457
|
+
|
|
458
|
+
**填写要求**:
|
|
459
|
+
- 所有需求必须来自 B1
|
|
460
|
+
- 必须标注优先级和依赖关系
|
|
461
|
+
- 必须说明哪些进入首版,哪些后续迭代
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## 1. 需求项列表
|
|
466
|
+
|
|
467
|
+
### 1.1 需求拆解
|
|
468
|
+
|
|
469
|
+
**需求项 #1**:
|
|
470
|
+
- 来源: (引用 B1 中的哪个需求)
|
|
471
|
+
- 描述:
|
|
472
|
+
- 优先级: P0 / P1 / P2
|
|
473
|
+
- 估算工作量:
|
|
474
|
+
|
|
475
|
+
**需求项 #2**:
|
|
476
|
+
<!-- 继续列举 -->
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## 2. 优先级排序
|
|
481
|
+
|
|
482
|
+
### 2.1 P0 (必须做)
|
|
483
|
+
|
|
484
|
+
1.
|
|
485
|
+
2.
|
|
486
|
+
3.
|
|
487
|
+
|
|
488
|
+
**理由**:
|
|
489
|
+
<!-- 说明为什么这些是 P0 -->
|
|
490
|
+
|
|
491
|
+
### 2.2 P1 (重要)
|
|
492
|
+
|
|
493
|
+
1.
|
|
494
|
+
2.
|
|
495
|
+
|
|
496
|
+
### 2.3 P2 (可选)
|
|
497
|
+
|
|
498
|
+
1.
|
|
499
|
+
2.
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## 3. 范围界定
|
|
504
|
+
|
|
505
|
+
### 3.1 首版包含
|
|
506
|
+
|
|
507
|
+
**进入首版的需求**:
|
|
508
|
+
- 需求项 #1
|
|
509
|
+
- 需求项 #2
|
|
510
|
+
- ...
|
|
511
|
+
|
|
512
|
+
**总工作量估算**:
|
|
513
|
+
|
|
514
|
+
### 3.2 后续迭代
|
|
515
|
+
|
|
516
|
+
**延后的需求**:
|
|
517
|
+
- 需求项 #X (延后原因: ______)
|
|
518
|
+
- 需求项 #Y (延后原因: ______)
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## 4. 依赖关系
|
|
523
|
+
|
|
524
|
+
### 4.1 前置依赖
|
|
525
|
+
|
|
526
|
+
**需求项 #1 依赖**:
|
|
527
|
+
- 依赖现有功能: (引用 A1)
|
|
528
|
+
- 依赖其他需求项:
|
|
529
|
+
|
|
530
|
+
### 4.2 阻塞风险
|
|
531
|
+
|
|
532
|
+
**已知风险**:
|
|
533
|
+
1.
|
|
534
|
+
2.
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## 5. 范围确认
|
|
539
|
+
|
|
540
|
+
### 5.1 确认声明
|
|
541
|
+
|
|
542
|
+
- [ ] 所有需求项均来自 B1
|
|
543
|
+
- [ ] 优先级排序已完成
|
|
544
|
+
- [ ] 首版范围已明确
|
|
545
|
+
- [ ] 依赖关系已标注
|
|
546
|
+
- [ ] 无范围膨胀
|
|
547
|
+
|
|
548
|
+
**范围签字**: _____________
|
|
549
|
+
**日期**: _____________
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## 备注
|
|
554
|
+
|
|
555
|
+
<!-- 其他需要说明的内容 -->
|
|
556
|
+
`;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* 从文档中提取指定标题下的内容
|
|
561
|
+
*/
|
|
562
|
+
function extractSection(content, sectionTitle) {
|
|
563
|
+
// 尝试匹配 "**标题**:" 或 "### 标题" 或 "## 标题" 格式
|
|
564
|
+
const patterns = [
|
|
565
|
+
new RegExp(`\\*\\*${sectionTitle}\\*\\*[:\\s]*([\\s\\S]*?)(?=\\n\\*\\*|\\n##|\\n---|\$)`, 'i'),
|
|
566
|
+
new RegExp(`###?\\s*${sectionTitle}[\\s\\S]*?\\n([\\s\\S]*?)(?=\\n##|\\n---|\$)`, 'i'),
|
|
567
|
+
new RegExp(`${sectionTitle}[:\\s]*\\n([\\s\\S]*?)(?=\\n\\*\\*|\\n##|\\n---|\$)`, 'i')
|
|
568
|
+
];
|
|
569
|
+
|
|
570
|
+
for (const pattern of patterns) {
|
|
571
|
+
const match = content.match(pattern);
|
|
572
|
+
if (match && match[1]) {
|
|
573
|
+
let extracted = match[1].trim();
|
|
574
|
+
// 清理 HTML 注释
|
|
575
|
+
extracted = extracted.replace(/<!--[\s\S]*?-->/g, '').trim();
|
|
576
|
+
// 清理空的占位符
|
|
577
|
+
extracted = extracted.replace(/_{3,}/g, '').trim();
|
|
578
|
+
if (extracted.length > 5) {
|
|
579
|
+
return extracted;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function getB3Template(pmSignature, extractedContent = {}) {
|
|
587
|
+
const {
|
|
588
|
+
b1CoreGoal = '(未提供)',
|
|
589
|
+
b2Scope = '(未提供)',
|
|
590
|
+
r1Summary = '(未提供)',
|
|
591
|
+
r1Conclusion = '✅ 通过'
|
|
592
|
+
} = extractedContent;
|
|
593
|
+
|
|
594
|
+
return `# B3_规划冻结归档
|
|
595
|
+
|
|
596
|
+
**冻结时间**: ${new Date().toLocaleString('zh-CN')}
|
|
597
|
+
**PM 签名**: ${pmSignature}
|
|
598
|
+
**文档状态**: 已冻结 ✅
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## 冻结声明
|
|
603
|
+
|
|
604
|
+
本规划已通过 R1 审视,正式冻结。
|
|
605
|
+
|
|
606
|
+
**冻结承诺**:
|
|
607
|
+
- 本轮迭代的规划目标已确定
|
|
608
|
+
- "不做的部分"已明确
|
|
609
|
+
- 后续版本(C 类)必须基于此规划
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
## 1. 规划总结
|
|
614
|
+
|
|
615
|
+
### 1.1 规划目标
|
|
616
|
+
|
|
617
|
+
**来自 B1 的核心目标**:
|
|
618
|
+
|
|
619
|
+
${b1CoreGoal}
|
|
620
|
+
|
|
621
|
+
### 1.2 范围说明
|
|
622
|
+
|
|
623
|
+
**来自 B2 的范围界定**:
|
|
624
|
+
|
|
625
|
+
${b2Scope}
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## 2. R1 审视结论
|
|
630
|
+
|
|
631
|
+
### 2.1 审视结果
|
|
632
|
+
|
|
633
|
+
**R1 审视状态**: ${r1Conclusion}
|
|
634
|
+
|
|
635
|
+
**通过时间**: ${new Date().toLocaleString('zh-CN')}
|
|
636
|
+
|
|
637
|
+
**审视摘要**:
|
|
638
|
+
|
|
639
|
+
${r1Summary}
|
|
640
|
+
|
|
641
|
+
### 2.2 待解决问题
|
|
642
|
+
|
|
643
|
+
**请参考 R1_规划审视报告.md 中的详细内容**
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
## 3. 版本化准备
|
|
648
|
+
|
|
649
|
+
### 3.1 进入 C 阶段的指引
|
|
650
|
+
|
|
651
|
+
**C0 版本范围声明应包含**:
|
|
652
|
+
- 基于上述规划目标
|
|
653
|
+
- 明确的版本边界
|
|
654
|
+
- 不超出本文档定义的范围
|
|
655
|
+
|
|
656
|
+
**C1 版本需求清单应包含**:
|
|
657
|
+
- B2 中首版包含的需求项
|
|
658
|
+
- 详细的验收标准
|
|
659
|
+
- 明确的实现路径
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## 4. 冻结管理
|
|
664
|
+
|
|
665
|
+
### 4.1 修改规则
|
|
666
|
+
|
|
667
|
+
**冻结后禁止**:
|
|
668
|
+
- ❌ 修改规划目标
|
|
669
|
+
- ❌ 扩大规划范围
|
|
670
|
+
- ❌ 引入新的核心需求
|
|
671
|
+
|
|
672
|
+
**允许调整**:
|
|
673
|
+
- ✅ C0/C1 中的细节描述
|
|
674
|
+
- ✅ 实现方案的优化
|
|
675
|
+
- ✅ 非核心的边界情况
|
|
676
|
+
|
|
677
|
+
### 4.2 解冻条件
|
|
678
|
+
|
|
679
|
+
**如需解冻规划**:
|
|
680
|
+
1. 必须说明解冻原因
|
|
681
|
+
2. 重新执行 R1 审视
|
|
682
|
+
3. 重新签字确认
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## 5. 交接信息
|
|
687
|
+
|
|
688
|
+
### 5.1 关键文档
|
|
689
|
+
|
|
690
|
+
- A0: 产品基础与范围说明
|
|
691
|
+
- A1: 已上线功能清单
|
|
692
|
+
- A2: 存量反馈汇总
|
|
693
|
+
- B1: 需求规划草案
|
|
694
|
+
- B2: 规划拆解与范围界定
|
|
695
|
+
- R1: 规划审视报告
|
|
696
|
+
|
|
697
|
+
### 5.2 下一步
|
|
698
|
+
|
|
699
|
+
1. 创建 C0_版本范围声明
|
|
700
|
+
2. 创建 C1_版本需求清单
|
|
701
|
+
3. 执行 R2_版本审视
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
**PM 最终确认**: ${pmSignature}
|
|
706
|
+
**冻结日期**: ${new Date().toLocaleDateString('zh-CN')}
|
|
707
|
+
**状态**: 🔒 已冻结
|
|
708
|
+
`;
|
|
709
|
+
}
|
|
710
|
+
|