prd-workflow-cli 1.4.0 → 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.
- package/.agent/workflows/prd-a1-scan.md +133 -0
- package/.agent/workflows/prd-a2ui-guide.md +6 -6
- package/.agent/workflows/prd-b-planning.md +135 -0
- package/.agent/workflows/prd-it-biz.md +56 -0
- package/.agent/workflows/prd-it-dev.md +163 -0
- package/.agent/workflows/prd-r2-review.md +104 -409
- package/.antigravity/rules.md +50 -265
- package/.cursorrules +57 -371
- package/GUIDE.md +147 -240
- package/README.md +170 -337
- package/bin/prd-cli.js +19 -12
- package/commands/baseline.js +174 -293
- package/commands/freeze-checks.js +424 -0
- package/commands/init.js +97 -162
- package/commands/it.js +286 -0
- package/commands/iteration.js +7 -91
- package/commands/planning.js +149 -517
- package/commands/review.js +78 -50
- package/commands/status.js +29 -38
- package/commands/upgrade.js +20 -0
- package/commands/version.js +222 -200
- package/package.json +2 -2
- package/rules/index.json +26 -27
- package/rules/schemas/rules.schema.json +1 -2
- package/templates/it-biz.md +141 -0
- package/templates/it-dev.md +237 -0
- package/templates//344/270/232/345/212/241/351/234/200/346/261/202.md +141 -0
- package/templates//346/212/200/346/234/257/350/247/204/346/240/274.md +237 -0
- package/.agent/workflows/prd-b1-planning-draft.md +0 -614
- package/.agent/workflows/prd-b2-planning-breakdown.md +0 -828
- package/.agent/workflows/prd-c1-requirement-list.md +0 -286
- package/.agent/workflows/prd-r1-review.md +0 -503
package/commands/planning.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
36
|
+
console.log('可用操作: create, freeze');
|
|
24
37
|
}
|
|
25
38
|
};
|
|
26
39
|
|
|
27
|
-
async function createPlanDoc(
|
|
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
|
|
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
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
71
|
+
// 写入规划文档模板
|
|
72
|
+
const template = getPlanningTemplate();
|
|
73
|
+
await fs.writeFile(filePath, template);
|
|
140
74
|
|
|
141
75
|
// 记录文档创建
|
|
142
|
-
await dialog.logDocumentCreation('planning',
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
//
|
|
208
|
-
|
|
209
|
-
if (!await fs.pathExists(
|
|
210
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
241
|
-
console.log(chalk.gray('
|
|
146
|
+
// 读取规划文档内容,提取关键信息
|
|
147
|
+
console.log(chalk.gray('正在从需求规划提取关键信息...'));
|
|
242
148
|
|
|
243
|
-
const
|
|
244
|
-
const b2Content = await fs.readFile(b2Path, 'utf-8');
|
|
149
|
+
const planContent = await fs.readFile(planPath, 'utf-8');
|
|
245
150
|
|
|
246
|
-
//
|
|
247
|
-
let
|
|
248
|
-
extractSection(
|
|
249
|
-
extractSection(b1Content, '规划目标') ||
|
|
151
|
+
// 提取核心问题
|
|
152
|
+
let coreGoal = extractSection(planContent, '要解决的问题') ||
|
|
153
|
+
extractSection(planContent, '核心问题') ||
|
|
250
154
|
'(请手动填写,未能自动提取)';
|
|
251
155
|
|
|
252
|
-
//
|
|
253
|
-
let
|
|
254
|
-
extractSection(
|
|
255
|
-
extractSection(b2Content, '包含范围') ||
|
|
156
|
+
// 提取需求拆解范围
|
|
157
|
+
let scope = extractSection(planContent, '需求拆解') ||
|
|
158
|
+
extractSection(planContent, '首版范围') ||
|
|
256
159
|
'(请手动填写,未能自动提取)';
|
|
257
160
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
285
|
-
|
|
166
|
+
|
|
167
|
+
const freezePath = path.join(iterationDir, '规划冻结.md');
|
|
168
|
+
await fs.writeFile(freezePath, freezeTemplate);
|
|
286
169
|
|
|
287
170
|
// 记录 PM 决策和文档创建
|
|
288
|
-
await dialog.logPMConfirmation('planning', '
|
|
171
|
+
await dialog.logPMConfirmation('planning', 'freeze', 'approved',
|
|
289
172
|
`PM签名: ${pmSignature}, 规划冻结`
|
|
290
173
|
);
|
|
291
|
-
await dialog.logDocumentCreation('planning', '
|
|
174
|
+
await dialog.logDocumentCreation('planning', '规划冻结', freezePath);
|
|
292
175
|
|
|
293
|
-
console.log(chalk.green('\n✓
|
|
294
|
-
console.log(chalk.cyan(`文件位置: ${
|
|
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.
|
|
299
|
-
console.log('2.
|
|
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
|
|
305
|
-
|
|
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
|
-
|
|
470
|
-
- 来源: (引用 B1 中的哪个需求)
|
|
471
|
-
- 描述:
|
|
472
|
-
- 优先级: P0 / P1 / P2
|
|
473
|
-
- 估算工作量:
|
|
199
|
+
在开始规划前,必须确认以下三点:
|
|
474
200
|
|
|
475
|
-
|
|
476
|
-
|
|
201
|
+
- [ ] **问题真实存在** - 在代码快照/用户反馈中有证据支持
|
|
202
|
+
- [ ] **值得单独规划** - 不是小修小补
|
|
203
|
+
- [ ] **问题已理解清楚** - 不是用规划来想问题
|
|
477
204
|
|
|
478
205
|
---
|
|
479
206
|
|
|
480
|
-
## 2.
|
|
481
|
-
|
|
482
|
-
### 2.1 P0 (必须做)
|
|
207
|
+
## 2. 核心问题
|
|
483
208
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
3.
|
|
209
|
+
**要解决的问题**:
|
|
210
|
+
<!-- 用一句话说明 -->
|
|
487
211
|
|
|
488
|
-
**理由**:
|
|
489
|
-
<!-- 说明为什么这些是 P0 -->
|
|
490
212
|
|
|
491
|
-
|
|
213
|
+
**期望达成的结果**:
|
|
214
|
+
<!-- 可衡量的目标 -->
|
|
492
215
|
|
|
493
|
-
1.
|
|
494
|
-
2.
|
|
495
216
|
|
|
496
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
-
|
|
518
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
2.
|
|
535
|
-
|
|
536
|
-
---
|
|
235
|
+
- [ ] 启动检查已通过
|
|
236
|
+
- [ ] 核心问题已明确
|
|
237
|
+
- [ ] 需求拆解完整
|
|
238
|
+
- [ ] 首版范围已确认
|
|
537
239
|
|
|
538
|
-
|
|
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
|
-
|
|
589
|
-
|
|
590
|
-
r1Summary = '(未提供)',
|
|
591
|
-
r1Conclusion = '✅ 通过'
|
|
247
|
+
coreGoal = '(未提供)',
|
|
248
|
+
scope = '(未提供)'
|
|
592
249
|
} = extractedContent;
|
|
593
250
|
|
|
594
|
-
return `#
|
|
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
|
-
|
|
261
|
+
本规划已通过启动检查,正式冻结。
|
|
605
262
|
|
|
606
263
|
**冻结承诺**:
|
|
607
264
|
- 本轮迭代的规划目标已确定
|
|
608
265
|
- "不做的部分"已明确
|
|
609
|
-
-
|
|
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
|
-
${
|
|
274
|
+
${coreGoal}
|
|
640
275
|
|
|
641
|
-
###
|
|
276
|
+
### 1.2 需求范围
|
|
642
277
|
|
|
643
|
-
|
|
278
|
+
${scope}
|
|
644
279
|
|
|
645
280
|
---
|
|
646
281
|
|
|
647
|
-
##
|
|
282
|
+
## 2. 进入 IT 阶段
|
|
648
283
|
|
|
649
|
-
|
|
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
|
-
##
|
|
291
|
+
## 3. 冻结管理
|
|
664
292
|
|
|
665
|
-
###
|
|
293
|
+
### 3.1 修改规则
|
|
666
294
|
|
|
667
295
|
**冻结后禁止**:
|
|
668
296
|
- ❌ 修改规划目标
|
|
@@ -670,41 +298,45 @@ ${r1Summary}
|
|
|
670
298
|
- ❌ 引入新的核心需求
|
|
671
299
|
|
|
672
300
|
**允许调整**:
|
|
673
|
-
- ✅
|
|
301
|
+
- ✅ IT 文档中的细节描述
|
|
674
302
|
- ✅ 实现方案的优化
|
|
675
303
|
- ✅ 非核心的边界情况
|
|
676
304
|
|
|
677
|
-
###
|
|
305
|
+
### 3.2 解冻条件
|
|
678
306
|
|
|
679
307
|
**如需解冻规划**:
|
|
680
308
|
1. 必须说明解冻原因
|
|
681
|
-
2.
|
|
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
|
+
}
|