prd-workflow-cli 1.4.1 → 2.0.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.
@@ -3,7 +3,12 @@ const path = require('path');
3
3
  const chalk = require('chalk');
4
4
  const confirm = require('./confirm');
5
5
  const dialog = require('./dialog');
6
+ const { runPlanFreezeChecks } = require('./freeze-checks');
6
7
 
8
+ /**
9
+ * 规划管理命令 (v2.0.0)
10
+ * 支持中文文件名:需求规划.md、规划冻结.md
11
+ */
7
12
  module.exports = async function (action, type, options = {}) {
8
13
  const configPath = path.join(process.cwd(), '.prd-config.json');
9
14
 
@@ -15,16 +20,24 @@ module.exports = async function (action, type, options = {}) {
15
20
  const config = await fs.readJSON(configPath);
16
21
 
17
22
  if (action === 'create') {
18
- await createPlanDoc(type, config, configPath, options);
23
+ // 检查废弃命令
24
+ if (type && (type.toUpperCase() === 'B1' || type.toUpperCase() === 'B2')) {
25
+ console.log(chalk.red(`✗ 命令已废弃: prd plan create ${type.toUpperCase()}`));
26
+ console.log(chalk.cyan('ℹ️ v2.0.0 以后,请使用: prd plan create'));
27
+ process.exitCode = 1;
28
+ return;
29
+ }
30
+ // B 参数兼容处理(静默忽略)
31
+ await createPlanDoc(config, configPath, options);
19
32
  } else if (action === 'freeze') {
20
33
  await freezePlan(config, configPath, options);
21
34
  } else {
22
35
  console.log(chalk.red('✗ 未知操作'));
23
- console.log('可用操作: create B1|B2, freeze');
36
+ console.log('可用操作: create, freeze');
24
37
  }
25
38
  };
26
39
 
27
- async function createPlanDoc(type, config, configPath, options = {}) {
40
+ async function createPlanDoc(config, configPath, options = {}) {
28
41
  if (config.currentIteration === 0) {
29
42
  console.log(chalk.red('✗ 请先创建迭代'));
30
43
  console.log('运行: prd iteration new');
@@ -37,18 +50,7 @@ async function createPlanDoc(type, config, configPath, options = {}) {
37
50
  `第${String(config.currentIteration).padStart(2, '0')}轮迭代`
38
51
  );
39
52
 
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);
53
+ const fileName = '需求规划.md';
52
54
  const filePath = path.join(iterationDir, fileName);
53
55
 
54
56
  if (await fs.pathExists(filePath)) {
@@ -56,131 +58,36 @@ async function createPlanDoc(type, config, configPath, options = {}) {
56
58
  return;
57
59
  }
58
60
 
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
- }
61
+ // 检查项目信息
62
+ const projectInfoPath = path.join(process.cwd(), '00_项目总览', '项目信息.md');
63
+ const oldP0Path = path.join(process.cwd(), '00_项目总览', 'P0_项目基本信息.md');
124
64
 
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
- }
65
+ if (!await fs.pathExists(projectInfoPath) && !await fs.pathExists(oldP0Path)) {
66
+ console.log(chalk.red('✗ 请先完成项目信息'));
67
+ console.log('文件位置: 00_项目总览/项目信息.md');
68
+ return;
136
69
  }
137
70
 
138
- // 写入文件
139
- await fs.writeFile(filePath, templates[type]);
71
+ // 写入规划文档模板
72
+ const template = getPlanningTemplate();
73
+ await fs.writeFile(filePath, template);
140
74
 
141
75
  // 记录文档创建
142
- await dialog.logDocumentCreation('planning', type, filePath);
76
+ await dialog.logDocumentCreation('planning', '需求规划', filePath);
143
77
 
144
78
  console.log(chalk.green(`✓ ${fileName} 创建成功!`));
145
79
  console.log(chalk.cyan(`文件位置: ${filePath}\n`));
146
80
 
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
- }
81
+ console.log(chalk.bold('📋 需求规划文档包含:'));
82
+ console.log(' 1. 启动检查');
83
+ console.log(' 2. 核心问题');
84
+ console.log(' 3. 需求拆解');
85
+ console.log(' 4. PM 确认\n');
86
+
87
+ console.log(chalk.bold('下一步:'));
88
+ console.log('1.AI 对话填写需求规划.md');
89
+ console.log('2. 填写完成后执行: prd plan freeze');
90
+ console.log('');
184
91
  }
185
92
 
186
93
  async function freezePlan(config, configPath, options = {}) {
@@ -195,34 +102,33 @@ async function freezePlan(config, configPath, options = {}) {
195
102
  `第${String(config.currentIteration).padStart(2, '0')}轮迭代`
196
103
  );
197
104
 
198
- // 检查 B1, B2 是否存在
199
- const b1Path = path.join(iterationDir, 'B1_需求规划草案.md');
200
- const b2Path = path.join(iterationDir, 'B2_规划拆解与范围界定.md');
105
+ // 支持 --force 跳过检查
106
+ if (options.force) {
107
+ console.log(chalk.yellow('\n⚠️ 使用 --force 跳过前置检查\n'));
108
+ } else {
109
+ // 执行自动检查(包含审视)
110
+ const checkResult = await runPlanFreezeChecks(iterationDir);
201
111
 
202
- if (!await fs.pathExists(b1Path) || !await fs.pathExists(b2Path)) {
203
- console.log(chalk.red(' 请先完成 B1 B2'));
204
- return;
112
+ if (!checkResult.pass) {
113
+ console.log(chalk.yellow('💡 提示:解决以上问题后重新运行 prd plan freeze'));
114
+ console.log(chalk.gray(' 或使用 prd plan freeze --force 强制跳过检查(不推荐)\n'));
115
+ return;
116
+ }
205
117
  }
206
118
 
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;
119
+ // 检查规划文档是否存在(支持新旧两种文件名)
120
+ let planPath = path.join(iterationDir, '需求规划.md');
121
+ if (!await fs.pathExists(planPath)) {
122
+ planPath = path.join(iterationDir, 'B_规划文档.md');
213
123
  }
214
124
 
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 审视'));
125
+ if (!await fs.pathExists(planPath)) {
126
+ console.log(chalk.red('✗ 请先创建需求规划'));
127
+ console.log('运行: prd plan create');
222
128
  return;
223
129
  }
224
130
 
225
- // ⭐ 支持预确认模式:PM 已在对话中确认并提供签名
131
+ // PM 确认冻结
226
132
  let pmSignature = null;
227
133
  if (options.pmConfirmed && options.pmSignature) {
228
134
  console.log(chalk.green(`✓ PM 已在对话中确认冻结,签名: ${options.pmSignature}`));
@@ -237,432 +143,154 @@ async function freezePlan(config, configPath, options = {}) {
237
143
  return;
238
144
  }
239
145
 
240
- // ⭐ 读取 B1、B2、R1 内容,提取关键信息
241
- console.log(chalk.gray('正在从 B1/B2/R1 提取关键信息...'));
146
+ // 读取规划文档内容,提取关键信息
147
+ console.log(chalk.gray('正在从需求规划提取关键信息...'));
242
148
 
243
- const b1Content = await fs.readFile(b1Path, 'utf-8');
244
- const b2Content = await fs.readFile(b2Path, 'utf-8');
149
+ const planContent = await fs.readFile(planPath, 'utf-8');
245
150
 
246
- // 提取 B1 核心目标(尝试从多个可能的标题下提取)
247
- let b1CoreGoal = extractSection(b1Content, '要解决的核心问题') ||
248
- extractSection(b1Content, '核心问题') ||
249
- extractSection(b1Content, '规划目标') ||
151
+ // 提取核心问题
152
+ let coreGoal = extractSection(planContent, '要解决的问题') ||
153
+ extractSection(planContent, '核心问题') ||
250
154
  '(请手动填写,未能自动提取)';
251
155
 
252
- // 提取 B2 范围说明
253
- let b2Scope = extractSection(b2Content, '首版包含') ||
254
- extractSection(b2Content, '范围界定') ||
255
- extractSection(b2Content, '包含范围') ||
156
+ // 提取需求拆解范围
157
+ let scope = extractSection(planContent, '需求拆解') ||
158
+ extractSection(planContent, '首版范围') ||
256
159
  '(请手动填写,未能自动提取)';
257
160
 
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
161
+ // 生成规划冻结文档
162
+ const freezeTemplate = getFreezeTemplate(pmSignature, {
163
+ coreGoal,
164
+ scope
283
165
  });
284
- const b3Path = path.join(iterationDir, 'B3_规划冻结归档.md');
285
- await fs.writeFile(b3Path, b3Template);
166
+
167
+ const freezePath = path.join(iterationDir, '规划冻结.md');
168
+ await fs.writeFile(freezePath, freezeTemplate);
286
169
 
287
170
  // 记录 PM 决策和文档创建
288
- await dialog.logPMConfirmation('planning', 'freeze_b3', 'approved',
171
+ await dialog.logPMConfirmation('planning', 'freeze', 'approved',
289
172
  `PM签名: ${pmSignature}, 规划冻结`
290
173
  );
291
- await dialog.logDocumentCreation('planning', 'B3', b3Path);
174
+ await dialog.logDocumentCreation('planning', '规划冻结', freezePath);
292
175
 
293
- console.log(chalk.green('\n✓ B3_规划冻结归档.md 创建成功!'));
294
- console.log(chalk.cyan(`文件位置: ${b3Path}\n`));
176
+ console.log(chalk.green('\n✓ 规划冻结.md 创建成功!'));
177
+ console.log(chalk.cyan(`文件位置: ${freezePath}\n`));
295
178
 
296
179
  console.log(chalk.bold.green('🎉 规划已冻结!\n'));
297
180
  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');
181
+ console.log('1. 创建 IT 用户故事: prd it create <名称>');
182
+ console.log('2. 所有 IT 完成后执行: prd version freeze');
301
183
  console.log('');
302
184
  }
303
185
 
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_规划拆解与范围界定
186
+ function getPlanningTemplate() {
187
+ return `# 需求规划
445
188
 
446
189
  **创建时间**: ${new Date().toLocaleString('zh-CN')}
447
- **文档状态**: 拆解中
448
190
 
449
191
  ---
450
192
 
451
- ## 文档说明
452
-
453
- **目的**:
454
- - 将 B1 的规划目标拆解为可执行的需求项
455
- - 确定优先级和范围
456
- - 界定清晰的版本边界
457
-
458
- **填写要求**:
459
- - 所有需求必须来自 B1
460
- - 必须标注优先级和依赖关系
461
- - 必须说明哪些进入首版,哪些后续迭代
193
+ > 与 AI 对话填写本文档
462
194
 
463
195
  ---
464
196
 
465
- ## 1. 需求项列表
466
-
467
- ### 1.1 需求拆解
197
+ ## 1. 启动检查
468
198
 
469
- **需求项 #1**:
470
- - 来源: (引用 B1 中的哪个需求)
471
- - 描述:
472
- - 优先级: P0 / P1 / P2
473
- - 估算工作量:
199
+ 在开始规划前,必须确认以下三点:
474
200
 
475
- **需求项 #2**:
476
- <!-- 继续列举 -->
201
+ - [ ] **问题真实存在** - 在代码快照/用户反馈中有证据支持
202
+ - [ ] **值得单独规划** - 不是小修小补
203
+ - [ ] **问题已理解清楚** - 不是用规划来想问题
477
204
 
478
205
  ---
479
206
 
480
- ## 2. 优先级排序
481
-
482
- ### 2.1 P0 (必须做)
207
+ ## 2. 核心问题
483
208
 
484
- 1.
485
- 2.
486
- 3.
209
+ **要解决的问题**:
210
+ <!-- 用一句话说明 -->
487
211
 
488
- **理由**:
489
- <!-- 说明为什么这些是 P0 -->
490
212
 
491
- ### 2.2 P1 (重要)
213
+ **期望达成的结果**:
214
+ <!-- 可衡量的目标 -->
492
215
 
493
- 1.
494
- 2.
495
216
 
496
- ### 2.3 P2 (可选)
217
+ **不做什么**:
218
+ <!-- 明确排除的范围 -->
497
219
 
498
- 1.
499
- 2.
500
220
 
501
221
  ---
502
222
 
503
- ## 3. 范围界定
504
-
505
- ### 3.1 首版包含
506
-
507
- **进入首版的需求**:
508
- - 需求项 #1
509
- - 需求项 #2
510
- - ...
511
-
512
- **总工作量估算**:
223
+ ## 3. 需求拆解
513
224
 
514
- ### 3.2 后续迭代
515
-
516
- **延后的需求**:
517
- - 需求项 #X (延后原因: ______)
518
- - 需求项 #Y (延后原因: ______)
225
+ | ID | 需求 | 优先级 | 首版 |
226
+ |----|------|-------|-----|
227
+ | REQ-001 | | P0 | ✅ |
228
+ | REQ-002 | | P1 | ❌ |
229
+ | | | | |
519
230
 
520
231
  ---
521
232
 
522
- ## 4. 依赖关系
523
-
524
- ### 4.1 前置依赖
525
-
526
- **需求项 #1 依赖**:
527
- - 依赖现有功能: (引用 A1)
528
- - 依赖其他需求项:
233
+ ## 4. PM 确认
529
234
 
530
- ### 4.2 阻塞风险
531
-
532
- **已知风险**:
533
- 1.
534
- 2.
535
-
536
- ---
235
+ - [ ] 启动检查已通过
236
+ - [ ] 核心问题已明确
237
+ - [ ] 需求拆解完整
238
+ - [ ] 首版范围已确认
537
239
 
538
- ## 5. 范围确认
539
-
540
- ### 5.1 确认声明
541
-
542
- - [ ] 所有需求项均来自 B1
543
- - [ ] 优先级排序已完成
544
- - [ ] 首版范围已明确
545
- - [ ] 依赖关系已标注
546
- - [ ] 无范围膨胀
547
-
548
- **范围签字**: _____________
549
- **日期**: _____________
550
-
551
- ---
552
-
553
- ## 备注
554
-
555
- <!-- 其他需要说明的内容 -->
240
+ **PM 签字**: ___________
241
+ **日期**: ___________
556
242
  `;
557
243
  }
558
244
 
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 = {}) {
245
+ function getFreezeTemplate(pmSignature, extractedContent = {}) {
587
246
  const {
588
- b1CoreGoal = '(未提供)',
589
- b2Scope = '(未提供)',
590
- r1Summary = '(未提供)',
591
- r1Conclusion = '✅ 通过'
247
+ coreGoal = '(未提供)',
248
+ scope = '(未提供)'
592
249
  } = extractedContent;
593
250
 
594
- return `# B3_规划冻结归档
251
+ return `# 规划冻结
595
252
 
596
253
  **冻结时间**: ${new Date().toLocaleString('zh-CN')}
597
254
  **PM 签名**: ${pmSignature}
598
- **文档状态**: 已冻结 ✅
255
+ **状态**: 已冻结 ✅
599
256
 
600
257
  ---
601
258
 
602
259
  ## 冻结声明
603
260
 
604
- 本规划已通过 R1 审视,正式冻结。
261
+ 本规划已通过启动检查,正式冻结。
605
262
 
606
263
  **冻结承诺**:
607
264
  - 本轮迭代的规划目标已确定
608
265
  - "不做的部分"已明确
609
- - 后续版本(C 类)必须基于此规划
266
+ - 后续 IT 文档必须基于此规划
610
267
 
611
268
  ---
612
269
 
613
270
  ## 1. 规划总结
614
271
 
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
- **审视摘要**:
272
+ ### 1.1 核心问题
638
273
 
639
- ${r1Summary}
274
+ ${coreGoal}
640
275
 
641
- ### 2.2 待解决问题
276
+ ### 1.2 需求范围
642
277
 
643
- **请参考 R1_规划审视报告.md 中的详细内容**
278
+ ${scope}
644
279
 
645
280
  ---
646
281
 
647
- ## 3. 版本化准备
282
+ ## 2. 进入 IT 阶段
648
283
 
649
- ### 3.1 进入 C 阶段的指引
650
-
651
- **C0 版本范围声明应包含**:
284
+ **创建 IT 用户故事时应包含**:
652
285
  - 基于上述规划目标
653
- - 明确的版本边界
286
+ - 明确的用户故事
654
287
  - 不超出本文档定义的范围
655
288
 
656
- **C1 版本需求清单应包含**:
657
- - B2 中首版包含的需求项
658
- - 详细的验收标准
659
- - 明确的实现路径
660
-
661
289
  ---
662
290
 
663
- ## 4. 冻结管理
291
+ ## 3. 冻结管理
664
292
 
665
- ### 4.1 修改规则
293
+ ### 3.1 修改规则
666
294
 
667
295
  **冻结后禁止**:
668
296
  - ❌ 修改规划目标
@@ -670,41 +298,45 @@ ${r1Summary}
670
298
  - ❌ 引入新的核心需求
671
299
 
672
300
  **允许调整**:
673
- - ✅ C0/C1 中的细节描述
301
+ - ✅ IT 文档中的细节描述
674
302
  - ✅ 实现方案的优化
675
303
  - ✅ 非核心的边界情况
676
304
 
677
- ### 4.2 解冻条件
305
+ ### 3.2 解冻条件
678
306
 
679
307
  **如需解冻规划**:
680
308
  1. 必须说明解冻原因
681
- 2. 重新执行 R1 审视
309
+ 2. 重新执行规划审视
682
310
  3. 重新签字确认
683
311
 
684
312
  ---
685
313
 
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
314
  **PM 最终确认**: ${pmSignature}
706
315
  **冻结日期**: ${new Date().toLocaleDateString('zh-CN')}
707
316
  **状态**: 🔒 已冻结
708
317
  `;
709
318
  }
710
319
 
320
+ /**
321
+ * 从文档中提取指定标题下的内容
322
+ */
323
+ function extractSection(content, sectionTitle) {
324
+ const patterns = [
325
+ new RegExp(`\\*\\*${sectionTitle}\\*\\*[:\\s]*([\\s\\S]*?)(?=\\n\\*\\*|\\n##|\\n---|$)`, 'i'),
326
+ new RegExp(`###?\\s*${sectionTitle}[\\s\\S]*?\\n([\\s\\S]*?)(?=\\n##|\\n---|$)`, 'i'),
327
+ new RegExp(`${sectionTitle}[:\\s]*\\n([\\s\\S]*?)(?=\\n\\*\\*|\\n##|\\n---|$)`, 'i')
328
+ ];
329
+
330
+ for (const pattern of patterns) {
331
+ const match = content.match(pattern);
332
+ if (match && match[1]) {
333
+ let extracted = match[1].trim();
334
+ extracted = extracted.replace(/<!--[\s\S]*?-->/g, '').trim();
335
+ extracted = extracted.replace(/_{3,}/g, '').trim();
336
+ if (extracted.length > 5) {
337
+ return extracted;
338
+ }
339
+ }
340
+ }
341
+ return null;
342
+ }